summaryrefslogtreecommitdiff
path: root/src/rebar_dialyzer_format.erl
diff options
context:
space:
mode:
authorFred Hebert <mononcqc@ferd.ca>2017-08-06 07:26:21 -0400
committerFred Hebert <mononcqc@ferd.ca>2017-08-06 07:26:21 -0400
commit963c49f5eb9ab5b34e1843fb43305743720917ac (patch)
tree6f7eab43592da16dd6c4d18072b1829851ab8500 /src/rebar_dialyzer_format.erl
parentb956c145938cab288d683b0977a2314e280ca02d (diff)
Unicode support in all the places
This is done through 3 main change groups: - replacing `~s` by `~ts` in format strings, so that strings that contain unicode are properly printed rather than crashing - adding the `unicode` argument to all function of the `re` module to ensure transformations on strings containing unicode data are valid instead of crashing (see issue #1302) - replacing `ec_cnv:to_binary/1` and `ec_cnv:to_list/1` with matching functions in `rebar_utils`. The last point has been done, rather than modifying and updating erlware commons, because binary and list conversions can be a contentious subject. For example, if what is being handled is actually bytes from a given binary stream, then forcing a byte-oriented interpretation of the data can corrupt it. As such, it does not appear safe to modify erlware commons' conversion functions since it may not be safe for all its users. Instead, rebar3 reimplements a subset of them (only converting atoms and chardata, ignoring numbers) with the explicit purpose of handling unicode string data. Tests were left as unchanged as possible. This may impact the ability to run rebar3's own suites in a unicode path, but respects a principle of least change for such a large patch.
Diffstat (limited to 'src/rebar_dialyzer_format.erl')
-rw-r--r--src/rebar_dialyzer_format.erl178
1 files changed, 89 insertions, 89 deletions
diff --git a/src/rebar_dialyzer_format.erl b/src/rebar_dialyzer_format.erl
index be8cc48..7cf4e63 100644
--- a/src/rebar_dialyzer_format.erl
+++ b/src/rebar_dialyzer_format.erl
@@ -31,7 +31,7 @@ format_warnings(Opts, Warnings) ->
format_warning_(_Opts, Warning = {_Tag, {File, Line}, Msg}, {File, Acc}) ->
try
String = message_to_string(Msg),
- {File, [lists:flatten(fmt("~!c~4w~!!: ~s", [Line, String])) | Acc]}
+ {File, [lists:flatten(fmt("~!c~4w~!!: ~ts", [Line, String])) | Acc]}
catch
Error:Reason ->
?DEBUG("Failed to pretty format warning: ~p:~p",
@@ -47,11 +47,11 @@ format_warning_(Opts, Warning = {_Tag, {SrcFile, Line}, Msg}, {_LastFile, Acc})
Dir = filename:dirname(File),
Root = filename:rootname(Base),
Ext = filename:extension(Base),
- Path = re:replace(Dir, "^.*/_build/", "_build/", [{return, list}]),
- Base1 = fmt("~!_c~s~!!~!__~s", [Root, Ext]),
- F = fmt("~!__~s", [filename:join(Path, Base1)]),
+ Path = re:replace(Dir, "^.*/_build/", "_build/", [{return, list}, unicode]),
+ Base1 = fmt("~!_c~ts~!!~!__~ts", [Root, Ext]),
+ F = fmt("~!__~ts", [filename:join(Path, Base1)]),
String = message_to_string(Msg),
- {SrcFile, [lists:flatten(fmt("~n~s~n~!c~4w~!!: ~s", [F, Line, String])) | Acc]}
+ {SrcFile, [lists:flatten(fmt("~n~ts~n~!c~4w~!!: ~ts", [F, Line, String])) | Acc]}
catch
Error:Reason ->
?DEBUG("Failed to pretty format warning: ~p:~p~n~p",
@@ -72,53 +72,53 @@ fmt(Fmt, Args) ->
%%----- Warnings for general discrepancies ----------------
message_to_string({apply, [Args, ArgNs, FailReason,
SigArgs, SigRet, Contract]}) ->
- fmt("~!^Fun application with arguments ~!!~s ",
+ fmt("~!^Fun application with arguments ~!!~ts ",
[bad_arg(ArgNs, Args)]) ++
call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract);
message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}) ->
- fmt("~!^The call~!! ~s:~s~s ~!^requires that"
- "~!! ~s ~!^is of type ~!g~s~!^ not ~!r~s",
+ fmt("~!^The call~!! ~ts:~ts~ts ~!^requires that"
+ "~!! ~ts ~!^is of type ~!g~ts~!^ not ~!r~ts",
[M, F, Args, Culprit, ExpectedType, FoundType]);
message_to_string({bin_construction, [Culprit, Size, Seg, Type]}) ->
- fmt("~!^Binary construction will fail since the ~!b~s~!^ field~!!"
- " ~s~!^ in segment~!! ~s~!^ has type~!! ~s",
+ fmt("~!^Binary construction will fail since the ~!b~ts~!^ field~!!"
+ " ~ts~!^ in segment~!! ~ts~!^ has type~!! ~ts",
[Culprit, Size, Seg, Type]);
message_to_string({call, [M, F, Args, ArgNs, FailReason,
SigArgs, SigRet, Contract]}) ->
- fmt("~!^The call~!! ~w:~w~s ", [M, F, bad_arg(ArgNs, Args)]) ++
+ fmt("~!^The call~!! ~w:~w~ts ", [M, F, bad_arg(ArgNs, Args)]) ++
call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract);
message_to_string({call_to_missing, [M, F, A]}) ->
fmt("~!^Call to missing or unexported function ~!!~w:~w/~w",
[M, F, A]);
message_to_string({exact_eq, [Type1, Op, Type2]}) ->
- fmt("~!^The test ~!!~s ~s ~s~!^ can never evaluate to 'true'",
+ fmt("~!^The test ~!!~ts ~ts ~ts~!^ can never evaluate to 'true'",
[Type1, Op, Type2]);
message_to_string({fun_app_args, [Args, Type]}) ->
- fmt("~!^Fun application with arguments ~!!~s~!^ will fail"
- " since the function has type ~!!~s", [Args, Type]);
+ fmt("~!^Fun application with arguments ~!!~ts~!^ will fail"
+ " since the function has type ~!!~ts", [Args, Type]);
message_to_string({fun_app_no_fun, [Op, Type, Arity]}) ->
- fmt("~!^Fun application will fail since ~!!~s ~!^::~!! ~s"
+ fmt("~!^Fun application will fail since ~!!~ts ~!^::~!! ~ts"
" is not a function of arity ~!!~w", [Op, Type, Arity]);
message_to_string({guard_fail, []}) ->
"~!^Clause guard cannot succeed.~!!";
message_to_string({guard_fail, [Arg1, Infix, Arg2]}) ->
- fmt("~!^Guard test ~!!~s ~s ~s~!^ can never succeed",
+ fmt("~!^Guard test ~!!~ts ~ts ~ts~!^ can never succeed",
[Arg1, Infix, Arg2]);
message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}) ->
- fmt("~!^Guard test not(~!!~s ~s ~s~!^) can never succeed",
+ fmt("~!^Guard test not(~!!~ts ~ts ~ts~!^) can never succeed",
[Arg1, Infix, Arg2]);
message_to_string({guard_fail, [Guard, Args]}) ->
- fmt("~!^Guard test ~!!~w~s~!^ can never succeed",
+ fmt("~!^Guard test ~!!~w~ts~!^ can never succeed",
[Guard, Args]);
message_to_string({neg_guard_fail, [Guard, Args]}) ->
- fmt("~!^Guard test not(~!!~w~s~!^) can never succeed",
+ fmt("~!^Guard test not(~!!~w~ts~!^) can never succeed",
[Guard, Args]);
message_to_string({guard_fail_pat, [Pat, Type]}) ->
- fmt("~!^Clause guard cannot succeed. The ~!!~s~!^ was matched"
- " against the type ~!!~s", [Pat, Type]);
+ fmt("~!^Clause guard cannot succeed. The ~!!~ts~!^ was matched"
+ " against the type ~!!~ts", [Pat, Type]);
message_to_string({improper_list_constr, [TlType]}) ->
fmt("~!^Cons will produce an improper list"
- " since its ~!b2~!!nd~!^ argument is~!! ~s", [TlType]);
+ " since its ~!b2~!!nd~!^ argument is~!! ~ts", [TlType]);
message_to_string({no_return, [Type|Name]}) ->
NameString =
case Name of
@@ -126,59 +126,59 @@ message_to_string({no_return, [Type|Name]}) ->
[F, A] -> fmt("~!^Function ~!r~w/~w ", [F, A])
end,
case Type of
- no_match -> fmt("~s~!^has no clauses that will ever match",[NameString]);
- only_explicit -> fmt("~s~!^only terminates with explicit exception", [NameString]);
- only_normal -> fmt("~s~!^has no local return", [NameString]);
- both -> fmt("~s~!^has no local return", [NameString])
+ no_match -> fmt("~ts~!^has no clauses that will ever match",[NameString]);
+ only_explicit -> fmt("~ts~!^only terminates with explicit exception", [NameString]);
+ only_normal -> fmt("~ts~!^has no local return", [NameString]);
+ both -> fmt("~ts~!^has no local return", [NameString])
end;
message_to_string({record_constr, [RecConstr, FieldDiffs]}) ->
- fmt("~!^Record construction ~!!~s~!^ violates the"
- " declared type of field ~!!~s", [RecConstr, FieldDiffs]);
+ fmt("~!^Record construction ~!!~ts~!^ violates the"
+ " declared type of field ~!!~ts", [RecConstr, FieldDiffs]);
message_to_string({record_constr, [Name, Field, Type]}) ->
fmt("~!^Record construction violates the declared type for ~!!#~w{}~!^"
- " since ~!!~s~!^ cannot be of type ~!!~s",
+ " since ~!!~ts~!^ cannot be of type ~!!~ts",
[Name, Field, Type]);
message_to_string({record_matching, [String, Name]}) ->
- fmt("~!^The ~!!~s~!^ violates the"
+ fmt("~!^The ~!!~ts~!^ violates the"
" declared type for ~!!#~w{}", [String, Name]);
message_to_string({record_match, [Pat, Type]}) ->
- fmt("~!^Matching of ~!!~s~!^ tagged with a record name violates the"
- " declared type of ~!!~s", [Pat, Type]);
+ fmt("~!^Matching of ~!!~ts~!^ tagged with a record name violates the"
+ " declared type of ~!!~ts", [Pat, Type]);
message_to_string({pattern_match, [Pat, Type]}) ->
- fmt("~!^The ~s~!^ can never match the type ~!g~s",
+ fmt("~!^The ~ts~!^ can never match the type ~!g~ts",
[bad_pat(Pat), Type]);
message_to_string({pattern_match_cov, [Pat, Type]}) ->
- fmt("~!^The ~s~!^ can never match since previous"
- " clauses completely covered the type ~!g~s",
+ fmt("~!^The ~ts~!^ can never match since previous"
+ " clauses completely covered the type ~!g~ts",
[bad_pat(Pat), Type]);
message_to_string({unmatched_return, [Type]}) ->
- fmt("~!^Expression produces a value of type ~!!~s~!^,"
+ fmt("~!^Expression produces a value of type ~!!~ts~!^,"
" but this value is unmatched", [Type]);
message_to_string({unused_fun, [F, A]}) ->
fmt("~!^Function ~!r~w/~w~!!~!^ will never be called", [F, A]);
%%----- Warnings for specs and contracts -------------------
message_to_string({contract_diff, [M, F, _A, Contract, Sig]}) ->
- fmt("~!^Type specification ~!!~w:~w~s~!^"
- " is not equal to the success typing: ~!!~w:~w~s",
+ fmt("~!^Type specification ~!!~w:~w~ts~!^"
+ " is not equal to the success typing: ~!!~w:~w~ts",
[M, F, Contract, M, F, Sig]);
message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}) ->
- fmt("~!^Type specification ~!!~w:~w~s~!^"
- " is a subtype of the success typing: ~!!~w:~w~s",
+ fmt("~!^Type specification ~!!~w:~w~ts~!^"
+ " is a subtype of the success typing: ~!!~w:~w~ts",
[M, F, Contract, M, F, Sig]);
message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) ->
- fmt("~!^Type specification ~!!~w:~w~s~!^"
- " is a supertype of the success typing: ~!!~w:~w~s",
+ fmt("~!^Type specification ~!!~w:~w~ts~!^"
+ " is a supertype of the success typing: ~!!~w:~w~ts",
[M, F, Contract, M, F, Sig]);
message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]}) ->
- fmt("~!^The contract ~!!~w:~w~s~!^ cannot be right because the"
- " inferred return for ~!!~w~s~!^ on line ~!!~w~!^ is ~!!~s",
+ fmt("~!^The contract ~!!~w:~w~ts~!^ cannot be right because the"
+ " inferred return for ~!!~w~ts~!^ on line ~!!~w~!^ is ~!!~ts",
[M, F, Contract, F, ArgStrings, Line, CRet]);
message_to_string({invalid_contract, [M, F, A, Sig]}) ->
fmt("~!^Invalid type specification for function~!! ~w:~w/~w."
- "~!^ The success typing is~!! ~s", [M, F, A, Sig]);
+ "~!^ The success typing is~!! ~ts", [M, F, A, Sig]);
message_to_string({extra_range, [M, F, A, ExtraRanges, SigRange]}) ->
fmt("~!^The specification for ~!!~w:~w/~w~!^ states that the function"
- " might also return ~!!~s~!^ but the inferred return is ~!!~s",
+ " might also return ~!!~ts~!^ but the inferred return is ~!!~ts",
[M, F, A, ExtraRanges, SigRange]);
message_to_string({overlapping_contract, [M, F, A]}) ->
fmt("~!^Overloaded contract for ~!!~w:~w/~w~!^ has overlapping"
@@ -189,62 +189,62 @@ message_to_string({spec_missing_fun, [M, F, A]}) ->
[M, F, A]);
%%----- Warnings for opaque type violations -------------------
message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}) ->
- fmt("~!^The call ~!!~w:~w~s~!^ contains ~!!~s~!^ when ~!!~s",
+ fmt("~!^The call ~!!~w:~w~ts~!^ contains ~!!~ts~!^ when ~!!~ts",
[M, F, bad_arg(ArgNs, Args), form_positions(ArgNs), form_expected(ExpArgs)]);
message_to_string({call_without_opaque, [M, F, Args, [{N,_,_}|_] = ExpectedTriples]}) ->
- fmt("~!^The call ~!!~w:~w~s ~!^does not have~!! ~s",
+ fmt("~!^The call ~!!~w:~w~ts ~!^does not have~!! ~ts",
[M, F, bad_arg(N, Args), form_expected_without_opaque(ExpectedTriples)]);
message_to_string({opaque_eq, [Type, _Op, OpaqueType]}) ->
- fmt("~!^Attempt to test for equality between a term of type ~!!~s~!^"
- " and a term of opaque type ~!!~s", [Type, OpaqueType]);
+ fmt("~!^Attempt to test for equality between a term of type ~!!~ts~!^"
+ " and a term of opaque type ~!!~ts", [Type, OpaqueType]);
message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}) ->
- fmt("~!^Guard test ~!!~s ~s ~s~!^ contains ~!!~s",
+ fmt("~!^Guard test ~!!~ts ~ts ~ts~!^ contains ~!!~ts",
[Arg1, Infix, Arg2, form_positions(ArgNs)]);
message_to_string({opaque_guard, [Guard, Args]}) ->
- fmt("~!^Guard test ~!!~w~s~!^ breaks the opaqueness of its"
+ fmt("~!^Guard test ~!!~w~ts~!^ breaks the opaqueness of its"
" argument", [Guard, Args]);
message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}) ->
Term = if OpaqueType =:= OpaqueTerm -> "the term";
true -> OpaqueTerm
end,
- fmt("~!^The attempt to match a term of type ~!!~s~!^ against the"
- "~!! ~s~!^ breaks the opaqueness of ~!!~s",
+ fmt("~!^The attempt to match a term of type ~!!~ts~!^ against the"
+ "~!! ~ts~!^ breaks the opaqueness of ~!!~ts",
[OpaqueType, Pat, Term]);
message_to_string({opaque_neq, [Type, _Op, OpaqueType]}) ->
- fmt("~!^Attempt to test for inequality between a term of type ~!!~s"
- "~!^ and a term of opaque type ~!!~s", [Type, OpaqueType]);
+ fmt("~!^Attempt to test for inequality between a term of type ~!!~ts"
+ "~!^ and a term of opaque type ~!!~ts", [Type, OpaqueType]);
message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}) ->
- fmt("~!^The type test ~!!~s~s~!^ breaks the opaqueness of the term "
- "~!!~s~s", [Fun, Args, Arg, ArgType]);
+ fmt("~!^The type test ~!!~ts~ts~!^ breaks the opaqueness of the term "
+ "~!!~ts~ts", [Fun, Args, Arg, ArgType]);
message_to_string({opaque_size, [SizeType, Size]}) ->
- fmt("~!^The size ~!!~s~!^ breaks the opaqueness of ~!!~s",
+ fmt("~!^The size ~!!~ts~!^ breaks the opaqueness of ~!!~ts",
[SizeType, Size]);
message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}) ->
- fmt("~!^The call ~!!~s:~s~s~!^ breaks the opaqueness of the term~!!"
- " ~s :: ~s", [M, F, Args, Culprit, OpaqueType]);
+ fmt("~!^The call ~!!~ts:~ts~ts~!^ breaks the opaqueness of the term~!!"
+ " ~ts :: ~ts", [M, F, Args, Culprit, OpaqueType]);
%%----- Warnings for concurrency errors --------------------
message_to_string({race_condition, [M, F, Args, Reason]}) ->
- fmt("~!^The call ~!!~w:~w~s ~s", [M, F, Args, Reason]);
+ fmt("~!^The call ~!!~w:~w~ts ~ts", [M, F, Args, Reason]);
%%----- Warnings for behaviour errors --------------------
message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}) ->
- fmt("~!^The inferred return type of~!! ~w/~w (~s) ~!^"
- "has nothing in common with~!! ~s, ~!^which is the expected"
+ fmt("~!^The inferred return type of~!! ~w/~w (~ts) ~!^"
+ "has nothing in common with~!! ~ts, ~!^which is the expected"
" return type for the callback of~!! ~w ~!^behaviour",
[F, A, ST, CT, B]);
message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}) ->
- fmt("~!^The inferred type for the~!! ~s ~!^argument of~!!"
- " ~w/~w (~s) ~!^is not a supertype of~!! ~s~!^, which is"
+ fmt("~!^The inferred type for the~!! ~ts ~!^argument of~!!"
+ " ~w/~w (~ts) ~!^is not a supertype of~!! ~ts~!^, which is"
"expected type for this argument in the callback of the~!! ~w "
"~!^behaviour",
[ordinal(N), F, A, ST, CT, B]);
message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}) ->
- fmt("~!^The return type ~!!~s~!^ in the specification of ~!!"
- "~w/~w~!^ is not a subtype of ~!!~s~!^, which is the expected"
+ fmt("~!^The return type ~!!~ts~!^ in the specification of ~!!"
+ "~w/~w~!^ is not a subtype of ~!!~ts~!^, which is the expected"
" return type for the callback of ~!!~w~!^ behaviour",
[ST, F, A, CT, B]);
message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}) ->
- fmt("~!^The specified type for the ~!!~s~!^ argument of ~!!"
- "~w/~w (~s)~!^ is not a supertype of ~!!~s~!^, which is"
+ fmt("~!^The specified type for the ~!!~ts~!^ argument of ~!!"
+ "~w/~w (~ts)~!^ is not a supertype of ~!!~ts~!^, which is"
" expected type for this argument in the callback of the ~!!~w"
"~!^ behaviour", [ordinal(N), F, A, ST, CT, B]);
message_to_string({callback_missing, [B, F, A]}) ->
@@ -274,26 +274,26 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
true ->
%% We do not know which argument(s) caused the failure
fmt("~!^will never return since the success typing arguments"
- " are ~!!~s", [SigArgs]);
+ " are ~!!~ts", [SigArgs]);
false ->
fmt("~!^will never return since it differs in the~!!"
- " ~s ~!^argument from the success typing"
- " arguments:~!! ~s",
+ " ~ts ~!^argument from the success typing"
+ " arguments:~!! ~ts",
[PositionString, good_arg(ArgNs, SigArgs)])
end;
only_contract ->
case (ArgNs =:= []) orelse IsOverloaded of
true ->
%% We do not know which arguments caused the failure
- fmt("~!^breaks the contract~!! ~s", [good_arg(ArgNs, Contract)]);
+ fmt("~!^breaks the contract~!! ~ts", [good_arg(ArgNs, Contract)]);
false ->
- fmt("~!^breaks the contract~!! ~s ~!^in the~!!"
- " ~s ~!^argument",
+ fmt("~!^breaks the contract~!! ~ts ~!^in the~!!"
+ " ~ts ~!^argument",
[good_arg(ArgNs, Contract), PositionString])
end;
both ->
fmt("~!^will never return since the success typing is "
- "~!!~s ~!^->~!! ~s ~!^and the contract is ~!!~s",
+ "~!!~ts ~!^->~!! ~ts ~!^and the contract is ~!!~ts",
[good_arg(ArgNs, SigArgs), SigRet,
good_arg(ArgNs, Contract)])
end.
@@ -301,8 +301,8 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
form_positions(ArgNs) ->
ArgS = form_position_string(ArgNs),
case ArgNs of
- [_] -> fmt("~!^an opaque term as ~!!~s~!^ argument", [ArgS]);
- [_,_|_] -> fmt("~!^opaque terms as ~!!~s~!^ arguments", [ArgS])
+ [_] -> fmt("~!^an opaque term as ~!!~ts~!^ argument", [ArgS]);
+ [_,_|_] -> fmt("~!^opaque terms as ~!!~ts~!^ arguments", [ArgS])
end.
%% We know which positions N are to blame;
@@ -310,9 +310,9 @@ form_positions(ArgNs) ->
form_expected_without_opaque([{N, T, TStr}]) ->
FStr = case erl_types:t_is_opaque(T) of
true ->
- "~!^an opaque term of type~!g ~s ~!^as ";
+ "~!^an opaque term of type~!g ~ts ~!^as ";
false ->
- "~!^a term of type ~!g~s ~!^(with opaque subterms) as "
+ "~!^a term of type ~!g~ts ~!^(with opaque subterms) as "
end ++ form_position_string([N]) ++ "~!^ argument",
fmt(FStr, [TStr]);
@@ -325,9 +325,9 @@ form_expected(ExpectedArgs) ->
[T] ->
TS = erl_types:t_to_string(T),
case erl_types:t_is_opaque(T) of
- true -> fmt("~!^an opaque term of type ~!!~s~!^ is"
+ true -> fmt("~!^an opaque term of type ~!!~ts~!^ is"
" expected", [TS]);
- false -> fmt("~!^a structured term of type ~!!~s~!^ is"
+ false -> fmt("~!^a structured term of type ~!!~ts~!^ is"
" expected", [TS])
end;
[_,_|_] -> fmt("~!^terms of different types are expected in these"
@@ -340,7 +340,7 @@ form_position_string(ArgNs) ->
[N1] -> ordinal(N1);
[_,_|_] ->
[Last|Prevs] = lists:reverse(ArgNs),
- ", " ++ Head = lists:flatten([fmt(", ~s",[ordinal(N)]) ||
+ ", " ++ Head = lists:flatten([fmt(", ~ts",[ordinal(N)]) ||
N <- lists:reverse(Prevs)]),
Head ++ " and " ++ ordinal(Last)
end.
@@ -352,11 +352,11 @@ ordinal(N) when is_integer(N) -> fmt("~!B~w~!!th", [N]).
%% Format a pattern ad highlight errorous part in red.
bad_pat("pattern " ++ P) ->
- fmt("pattern ~!r~s",[P]);
+ fmt("pattern ~!r~ts",[P]);
bad_pat("variable " ++ P) ->
- fmt("variable ~!r~s",[P]);
+ fmt("variable ~!r~ts",[P]);
bad_pat(P) ->
- fmt("~!r~s",[P]).
+ fmt("~!r~ts",[P]).
bad_arg(N, Args) ->
@@ -378,10 +378,10 @@ highlight([], _N, _C, Rest) ->
Rest;
highlight([N | Nr], N, g, [Arg | Rest]) ->
- [fmt("~!g~s", [Arg]) | highlight(Nr, N+1, g, Rest)];
+ [fmt("~!g~ts", [Arg]) | highlight(Nr, N+1, g, Rest)];
highlight([N | Nr], N, r, [Arg | Rest]) ->
- [fmt("~!r~s", [Arg]) | highlight(Nr, N+1, r, Rest)];
+ [fmt("~!r~ts", [Arg]) | highlight(Nr, N+1, r, Rest)];
highlight(Ns, N, C, [Arg | Rest]) ->
[Arg | highlight(Ns, N + 1, C, Rest)].