27 changes: 18 additions & 9 deletions lib/stdlib/src/erl_lint.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2107,6 +2107,9 @@ expr({block,_Line,Es}, Vt, St) ->
exprs(Es, Vt, St);
expr({'if',Line,Cs}, Vt, St) ->
icrt_clauses(Cs, {'if',Line}, Vt, St);
expr({'cond',Line,Cs}, Vt, St) ->
GuardF = fun ([[E]], Vt0, St0) -> expr(E, Vt0, St0) end,
icrt_clauses(Cs, {'cond',Line}, GuardF, Vt, St);
expr({'case',Line,E,Cs}, Vt, St0) ->
{Evt,St1} = expr(E, Vt, St0),
{Cvt,St2} = icrt_clauses(Cs, {'case',Line}, vtupdate(Evt, Vt), St1),
Expand All @@ -2117,7 +2120,7 @@ expr({'receive',Line,Cs,To,ToEs}, Vt, St0) ->
%% Are variables from the timeout expression visible in the clauses? NO!
{Tvt,St1} = expr(To, Vt, St0),
{Tevt,St2} = exprs(ToEs, Vt, St1),
{Cvt,St3} = icrt_clauses(Cs, Vt, St2),
{Cvt,St3} = clauses(Cs, Vt, St2),
%% Csvts = [vtnew(Tevt, Vt)|Cvt], %This is just NEW variables!
Csvts = [Tevt|Cvt],
Rvt = icrt_export(Csvts, Vt, {'receive',Line}),
Expand Down Expand Up @@ -3010,21 +3013,27 @@ check_local_opaque_types(St) ->
%% icrt_clauses(Clauses, In, ImportVarTable, State) ->
%% {UpdVt,State}.

icrt_clauses(Cs, In, Vt, St0) ->
{Csvt,St1} = icrt_clauses(Cs, Vt, St0),
icrt_clauses(Cs, In, Vt, St) ->
icrt_clauses(Cs, In, fun guard/3, Vt, St).

icrt_clauses(Cs, In, GuardF, Vt, St0) ->
{Csvt,St1} = clauses(Cs, GuardF, Vt, St0),
UpdVt = icrt_export(Csvt, Vt, In),
{UpdVt,St1}.

%% icrt_clauses(Clauses, ImportVarTable, State) ->
%% clauses(Clauses, ImportVarTable, State) ->
%% {NewVts,State}.

icrt_clauses(Cs, Vt, St) ->
mapfoldl(fun (C, St0) -> icrt_clause(C, Vt, St0) end, St, Cs).
clauses(Cs, Vt, St) ->
clauses(Cs, fun guard/3, Vt, St).

clauses(Cs, GuardF, Vt, St) ->
mapfoldl(fun (C, St0) -> clause(C, GuardF, Vt, St0) end, St, Cs).

icrt_clause({clause,_Line,H,G,B}, Vt0, St0) ->
clause({clause,_Line,H,G,B}, GuardF, Vt0, St0) ->
{Hvt,Binvt,St1} = head(H, Vt0, St0),
Vt1 = vtupdate(Hvt, Binvt),
{Gvt,St2} = guard(G, vtupdate(Vt1, Vt0), St1),
Vt1 = vtupdate(Hvt, vtupdate(Binvt, Vt0)),
{Gvt,St2} = GuardF(G, Vt1, St1),
Vt2 = vtupdate(Gvt, Vt1),
{Bvt,St3} = exprs(B, vtupdate(Vt2, Vt0), St2),
{vtupdate(Bvt, Vt2),St3}.
Expand Down
12 changes: 12 additions & 0 deletions lib/stdlib/src/erl_parse.yrl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ tuple
record_expr record_tuple record_field record_fields
map_expr map_tuple map_field map_field_assoc map_field_exact map_fields map_key
if_expr if_clause if_clauses case_expr cr_clause cr_clauses receive_expr
cond_expr cond_clauses cond_clause
fun_expr fun_clause fun_clauses atom_or_var integer_or_var
try_expr try_catch try_clause try_clauses
function_call argument_list
Expand All @@ -55,6 +56,7 @@ char integer float atom string var

'(' ')' ',' '->' '{' '}' '[' ']' '|' '||' '<-' ';' ':' '#' '.'
'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when'
'cond'
'andalso' 'orelse'
'bnot' 'not'
'*' '/' 'div' 'rem' 'band' 'and'
Expand Down Expand Up @@ -274,6 +276,7 @@ expr_max -> tuple : '$1'.
expr_max -> '(' expr ')' : '$2'.
expr_max -> 'begin' exprs 'end' : {block,?line('$1'),'$2'}.
expr_max -> if_expr : '$1'.
expr_max -> cond_expr : '$1'.
expr_max -> case_expr : '$1'.
expr_max -> receive_expr : '$1'.
expr_max -> fun_expr : '$1'.
Expand Down Expand Up @@ -399,6 +402,15 @@ if_clause -> guard clause_body :
{clause,?line(hd(hd('$1'))),[],'$1','$2'}.


cond_expr -> 'cond' cond_clauses 'end' : {'cond',?line('$1'),'$2'}.

cond_clauses -> cond_clause : ['$1'].
cond_clauses -> cond_clause ';' cond_clauses : ['$1' | '$3'].

cond_clause -> expr clause_body :
{clause,?line('$1'),[],[['$1']],'$2'}.


case_expr -> 'case' expr 'of' cr_clauses 'end' :
{'case',?line('$1'),'$2','$4'}.

Expand Down
1 change: 1 addition & 0 deletions lib/stdlib/src/ms_transform.erl
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ translate_language_element(Atom) ->
{bc,"binary comprehension"},
{block, "begin/end block"},
{'if', "if"},
{'cond', "cond"},
{'case', "case"},
{'receive', "receive"},
{'try', "try"},
Expand Down
2 changes: 2 additions & 0 deletions lib/stdlib/src/shell.erl
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ expand_expr({block,L,Es}, C) ->
{block,L,expand_exprs(Es, C)};
expand_expr({'if',L,Cs}, C) ->
{'if',L,expand_cs(Cs, C)};
expand_expr({'cond',L,Cs}, C) ->
{'cond',L,expand_cs(Cs, C)};
expand_expr({'case',L,E,Cs}, C) ->
{'case',L,expand_expr(E, C),expand_cs(Cs, C)};
expand_expr({'try',L,Es,Scs,Ccs,As}, C) ->
Expand Down
32 changes: 30 additions & 2 deletions lib/stdlib/test/erl_eval_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
eval_expr_5/1,
zero_width/1,
eep37/1,
eep43/1]).
eep43/1,
'cond'/1]).

%%
%% Define to run outside of test server
Expand Down Expand Up @@ -83,7 +84,7 @@ all() ->
simple_cases, unary_plus, apply_atom, otp_5269,
otp_6539, otp_6543, otp_6787, otp_6977, otp_7550,
otp_8133, otp_10622, funs, try_catch, eval_expr_5, zero_width,
eep37, eep43].
eep37, eep43, 'cond'].

groups() ->
[].
Expand Down Expand Up @@ -1486,6 +1487,33 @@ eep43(Config) when is_list(Config) ->
error_check("#{} = 1.", {badmatch,1}),
ok.

'cond'(Config) when is_list(Config) ->
check(fun () -> cond true -> ok end end,
"cond true -> ok end.",
ok),
check(fun () -> catch cond throw(not_ok) -> ok end end,
"catch cond throw(not_ok) -> ok end.",
not_ok),
check(fun () ->
{'EXIT',{{badbool,hello},_}} = (catch cond hello -> ok end),
done
end,
"begin "
" {'EXIT',{{badbool,hello},_}} = (catch cond hello -> ok end), "
" done "
"end.",
done),
check(fun () ->
{'EXIT',{cond_clause,_}} = (catch cond false -> ok end),
done
end,
"begin "
" {'EXIT',{cond_clause,_}} = (catch cond false -> ok end), "
" done "
"end.",
done),
ok.

%% Check the string in different contexts: as is; in fun; from compiled code.
check(F, String, Result) ->
check1(F, String, Result),
Expand Down
15 changes: 14 additions & 1 deletion lib/stdlib/test/erl_expand_records_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
-export([attributes/1, expr/1, guard/1,
init/1, pattern/1, strict/1, update/1,
otp_5915/1, otp_7931/1, otp_5990/1,
otp_7078/1, otp_7101/1, maps/1]).
otp_7078/1, otp_7101/1, maps/1, 'cond'/1]).

% Default timetrap timeout (set in init_per_testcase).
-define(default_timeout, ?t:minutes(1)).
Expand Down Expand Up @@ -419,6 +419,19 @@ maps(Config) when is_list(Config) ->
run(Config, Ts, [strict_record_tests]),
ok.

'cond'(Config) when is_list(Config) ->
Ts = [<<"-record(rr, {a=0,b,c}).
t() ->
R0 = id(#rr{a=1,b=2,c=3}),
cond is_record(R0, rr) -> R0#rr.a;
R0#rr.a =:= #rr{}#rr.a -> R0#rr.b
end,
ok.
id(X) -> X.">>],
run(Config, Ts, [strict_record_tests]),
ok.

otp_5915(doc) ->
"Strict record tests in guards.";
otp_5915(suite) -> [];
Expand Down
12 changes: 10 additions & 2 deletions lib/stdlib/test/erl_lint_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
too_many_arguments/1,
basic_errors/1,bin_syntax_errors/1,
predef/1,
maps/1,maps_type/1,otp_11851/1
maps/1,maps_type/1,otp_11851/1,'cond'/1
]).

% Default timetrap timeout (set in init_per_testcase).
Expand Down Expand Up @@ -93,7 +93,7 @@ all() ->
bif_clash, behaviour_basic, behaviour_multiple, otp_11861,
otp_7550, otp_8051, format_warn, {group, on_load},
too_many_arguments, basic_errors, bin_syntax_errors, predef,
maps, maps_type, otp_11851].
maps, maps_type, otp_11851, 'cond'].

groups() ->
[{unused_vars_warn, [],
Expand Down Expand Up @@ -3834,6 +3834,14 @@ otp_11851(Config) when is_list(Config) ->
[] = run(Config, Ts),
ok.

'cond'(Config) when is_list(Config) ->
Ts = [{cond_1,
<<"t(A) -> cond A =:= true -> ok; A =:= false -> error end.">>,
[],
[]}],
[] = run(Config, Ts),
ok.

run(Config, Tests) ->
F = fun({N,P,Ws,E}, BadL) ->
case catch run_test(Config, P, Ws) of
Expand Down
8 changes: 7 additions & 1 deletion lib/stdlib/test/ms_transform_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
-export([warnings/1]).
-export([no_warnings/1]).
-export([eep37/1]).
-export(['cond'/1]).
-export([init_per_testcase/2, end_per_testcase/2]).

init_per_testcase(_Func, Config) ->
Expand All @@ -58,7 +59,7 @@ all() ->
record_index, multipass, bitsyntax, record_defaults,
andalso_orelse, float_1_function, action_function,
warnings, no_warnings, top_match, old_guards, autoimported,
semicolon, eep37].
semicolon, eep37, 'cond'].

groups() ->
[].
Expand Down Expand Up @@ -817,6 +818,11 @@ eep37(Config) when is_list(Config) ->
" end,\n"
"F()">>).

'cond'(Config) when is_list(Config) ->
setup(Config),
[{'$1',[],['$1']}] =
compile_and_run(<<"cond true -> ets:fun2ms(fun (X) -> X end) end">>).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down
5 changes: 3 additions & 2 deletions lib/tools/emacs/erlang.el
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@ resulting regexp is surrounded by \\_< and \\_>."
"byte_size"
"check_old_code"
"check_process_code"
"cond"
"date"
"delete_module"
"demonitor"
Expand Down Expand Up @@ -2584,7 +2585,7 @@ Value is list (stack token-start token-type in-what)."
(if (and stack (memq (car (car stack)) '(icr begin try)))
(erlang-pop stack))))
)
(cond ((looking-at "\\(if\\|case\\|receive\\)[^_a-zA-Z0-9]")
(cond ((looking-at "\\(if\\|case\\|cond\\|receive\\)[^_a-zA-Z0-9]")
;; Must push a new icr (if/case/receive) layer.
(erlang-push (list 'icr token (current-column)) stack))
((looking-at "\\(try\\|after\\)[^_a-zA-Z0-9]")
Expand Down Expand Up @@ -3126,7 +3127,7 @@ This assumes that the preceding expression is either simple

(defun erlang-at-keyword ()
"Are we looking at an Erlang keyword which will increase indentation?"
(looking-at (concat "\\(when\\|if\\|fun\\|case\\|begin\\|"
(looking-at (concat "\\(when\\|if\\|fun\\|case\\|cond\\|begin\\|"
"of\\|receive\\|after\\|catch\\|try\\)\\b")))

(defun erlang-at-operator ()
Expand Down
16 changes: 16 additions & 0 deletions lib/tools/emacs/test.erl.indented
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,22 @@ indent_icr(Z) -> % icr = if case receive
ok
end,

%% test cond keyword
cond
A ->
ok;
B ->
true;
C ->
false
end,
cond is_binary(A) ->
A;
is_binary(B) ->
B;
true ->
{A,B}
end,

%% receive
receive
Expand Down
16 changes: 16 additions & 0 deletions lib/tools/emacs/test.erl.orig
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,22 @@ indent_icr(Z) -> % icr = if case receive
ok
end,

%% test cond keyword
cond
A ->
ok;
B ->
true;
C ->
false
end,
cond is_binary(A) ->
A;
is_binary(B) ->
B;
true ->
{A,B}
end,

%% receive
receive
Expand Down
6 changes: 6 additions & 0 deletions lib/tools/src/cover.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1838,6 +1838,9 @@ fix_last_expr([MungedExpr|MungedExprs], Line, Vars) ->
fix_expr({'if',L,Clauses}, Line, Bump) ->
FixedClauses = fix_clauses(Clauses, Line, Bump),
{'if',L,FixedClauses};
fix_expr({'cond',L,Clauses}, Line, Bump) ->
FixedClauses = fix_clauses(Clauses, Line, Bump),
{'cond',L,FixedClauses};
fix_expr({'case',L,Expr,Clauses}, Line, Bump) ->
FixedExpr = fix_expr(Expr, Line, Bump),
FixedClauses = fix_clauses(Clauses, Line, Bump),
Expand Down Expand Up @@ -1992,6 +1995,9 @@ munge_expr({block,Line,Body}, Vars) ->
munge_expr({'if',Line,Clauses}, Vars) ->
{MungedClauses,Vars2} = munge_clauses(Clauses, Vars),
{{'if',Line,MungedClauses}, Vars2};
munge_expr({'cond',Line,Clauses}, Vars) ->
{MungedClauses,Vars2} = munge_clauses(Clauses, Vars),
{{'cond',Line,MungedClauses}, Vars2};
munge_expr({'case',Line,Expr,Clauses}, Vars) ->
{MungedExpr,Vars2} = munge_expr(Expr, Vars),
{MungedClauses,Vars3} = munge_clauses(Clauses, Vars2),
Expand Down
16 changes: 9 additions & 7 deletions lib/tools/src/xref_reader.erl
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,19 @@ form({function, Line, Name, Arity, Clauses}, S) ->
S3 = clauses(Clauses, S2),
S3#xrefr{function = []}.

clauses(Cls, S) ->
#xrefr{funvars = FunVars, matches = Matches} = S,
clauses(Cls, FunVars, Matches, S).
clauses(Cs, #xrefr{builtins_too=ShouldScanGuards}=S) ->
clauses(Cs, S, ShouldScanGuards).

clauses([{clause, _Line, _H, G, B} | Cs], FunVars, Matches, S) ->
S1 = case S#xrefr.builtins_too of
clauses([{clause, _Line, _H, G, B} | Cs], S, ShouldScanGuards) ->
#xrefr{funvars = FunVars, matches = Matches} = S,
S1 = case ShouldScanGuards of
true -> expr(G, S);
false -> S
end,
S2 = expr(B, S1),
S3 = S2#xrefr{funvars = FunVars, matches = Matches},
clauses(Cs, S3);
clauses([], _FunVars, _Matches, S) ->
clauses(Cs, S3, ShouldScanGuards);
clauses([], S, _ShouldScanGuards) ->
S.

attr([E={From, To} | As], Ln, M, Fun, AL, AX, B, S) ->
Expand Down Expand Up @@ -138,6 +138,8 @@ mfa(_, _M) -> false.

expr({'if', _Line, Cs}, S) ->
clauses(Cs, S);
expr({'cond', _Line, Cs}, S) ->
clauses(Cs, S, true);
expr({'case', _Line, E, Cs}, S) ->
S1 = expr(E, S),
clauses(Cs, S1);
Expand Down
16 changes: 14 additions & 2 deletions lib/tools/test/cover_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
export_import/1,
otp_5031/1, eif/1, otp_5305/1, otp_5418/1, otp_6115/1, otp_7095/1,
otp_8188/1, otp_8270/1, otp_8273/1, otp_8340/1,
otp_10979_hanging_node/1, compile_beam_opts/1, eep37/1]).
otp_10979_hanging_node/1, compile_beam_opts/1, eep37/1, 'cond'/1]).

-export([do_coverage/1]).

Expand All @@ -52,7 +52,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}].

all() ->
NoStartStop = [eif,otp_5305,otp_5418,otp_7095,otp_8273,
otp_8340,otp_8188,compile_beam_opts,eep37],
otp_8340,otp_8188,compile_beam_opts,eep37,'cond'],
StartStop = [start, compile, analyse, misc, stop,
distribution, reconnect, die_and_reconnect,
dont_reconnect_after_stop, stop_node_after_disconnect,
Expand Down Expand Up @@ -1634,6 +1634,18 @@ eep37(Config) when is_list(Config) ->
Config),
ok.

'cond'(Config) when is_list(Config) ->
[{{t,1},1},{{t,2},1},{{t,3},0},{{t,5},1}] =
analyse_expr(<<"begin\n" % 1
" cond lists:member(foo, [bar]) ->\n" % 1
" foo_in_list;\n" % 0
" true ->\n"
" foo_not_in_list\n" % 1
" end\n"
"end\n">>,
Config),
ok.

otp_10979_hanging_node(_Config) ->

P1 = processes(),
Expand Down
12 changes: 9 additions & 3 deletions lib/tools/test/xref_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,7 @@ read_expected(Version) ->
POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10,
POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8,
POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10,
POS14 = POS13+18, POS15 = POS14+23,
POS14 = POS13+18, POS15 = POS14+23, POS16 = POS15+12,

FF = {read,funfuns,0},
U = [{POS1+5,{FF,{dist,'$F_EXPR',0}}},
Expand Down Expand Up @@ -1197,7 +1197,11 @@ read_expected(Version) ->
++ O1;
_ ->
[{16,{FF,{read,'$F_EXPR',178}}},
{17,{FF,{modul,'$F_EXPR',179}}}]
{17,{FF,{modul,'$F_EXPR',179}}},
{POS16+1,{{read,bi,0},{m,f,0}}},
{POS16+2,{{read,bi,0},{io,format,1}}},
{POS16+4,{{read,bi,0},{io,format,1}}},
{POS16+6,{{read,bi,0},{io,format,1}}}]
++
O1
end,
Expand Down Expand Up @@ -1226,7 +1230,9 @@ read_expected(Version) ->
{POS15+1, {{read,bi,0},{erlang,'>',2}}},
{POS15+2, {{read,bi,0},{erlang,'-',2}}},
{POS15+2, {{read,bi,0},{erlang,'*',2}}},
{POS15+8, {{read,bi,0},{erlang,'/',2}}}]
{POS15+8, {{read,bi,0},{erlang,'/',2}}},
{POS16+3, {{read,bi,0},{erlang,'+',2}}},
{POS16+3, {{read,bi,0},{erlang,'=:=',2}}}]
end
++ [{POS14+19, {{read,bi,0},{erlang,'+',2}}},
{POS14+21, {{read,bi,0},{erlang,'+',2}}},
Expand Down
11 changes: 10 additions & 1 deletion lib/tools/test/xref_SUITE_data/read/read.erl
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,16 @@ bi() ->
G = fun _(foo) -> bar;
_(X) -> X / 3
end,
G(foo).
G(foo);
bi() ->
%% Cond. POS16=POS15+12
cond m:f() ->
io:format("foo!");
1 + 2 =:= 3 ->
io:format("maths still hold.");
true ->
io:format("not foo!")
end.

local() ->
true.
32 changes: 32 additions & 0 deletions system/doc/reference_manual/expressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,38 @@ is_greater_than(X, Y) ->
end</pre>
</section>

<section>
<marker id="cond"></marker>
<title>Cond</title>
<pre>
cond Expr1 ->
Body1;
...;
ExprN ->
BodyN
end</pre>
<p>The branches of an <c>cond</c>-expression are scanned sequentially
until an expression <c>Expr</c> which evaluates to true is found. Then the
corresponding <c>Body</c> is evaluated.</p>
<p>The return value of <c>Body</c> is the return value of
the <c>cond</c> expression.</p>
<p>If no expression is true, a <c>cond_clause</c> run-time error will occur.
If necessary, the expression <c>true</c> can be used in the last branch,
as that expression is always true.</p>
<p>Arbitrary expressions are allowed, if one of them evaluates to a
non-boolean value, a <c>badbool</c> run-time error will occur.</p>
<p>Example:</p>
<pre>
manage_engine() ->
cond voltage_nominal() ->
continue_operations();
in_emergency() ->
set_speed_slow();
true ->
shut_device_down()
end.</pre>
</section>

<section>
<marker id="case"></marker>
<title>Case</title>
Expand Down