diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl index f06055dbc1d2..113c05e8bfe7 100644 --- a/lib/stdlib/src/shell.erl +++ b/lib/stdlib/src/shell.erl @@ -430,15 +430,15 @@ get_command(Prompt, Eval, Bs, RT, FT, Ds) -> case Toks of [{'-', _}, {atom, _, Atom}|_] -> SpecialCase = fun(LocalFunc) -> - FakeLine = begin - case erl_parse:parse_form(Toks) of - {ok, Def} -> lists:flatten(escape_quotes(lists:flatten(erl_pp:form(Def)))); - E -> - exit(E) - end - end, + FakeLine = + case erl_parse:parse_form(Toks) of + {ok, Def} -> + lists:flatten(erl_pp:form(Def, enc())); + E -> + exit(E) + end, {done, {ok, FakeResult, _}, _} = erl_scan:tokens( - [], atom_to_list(LocalFunc) ++ "(\""++FakeLine++"\").\n", + [], atom_to_list(LocalFunc) ++ "("++ max_consecutive_quotes(FakeLine) ++ ").\n", {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]), erl_eval:extended_parse_exprs(FakeResult) end, @@ -455,11 +455,14 @@ get_command(Prompt, Eval, Bs, RT, FT, Ds) -> FunName1 = lists:flatten(io_lib:fwrite("~tw",[FunName])), case {edlin_expand:shell_default_or_bif(FunName1), shell:local_func(FunName1)} of {"user_defined", false} -> - FunDef1 = lists:flatten(escape_quotes(lists:flatten(erl_pp:form(FunDef)))), - FakeLine = reconstruct(FunDef, FunName), - {done, {ok, FakeResult, _}, _} = erl_scan:tokens( - [], "fd("++ FunName1 ++ ", " ++ FakeLine ++ ", \"" ++ FunDef1 ++ "\").\n", - {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]), + + FunDef1 = lists:flatten(erl_pp:form(FunDef, enc())), + FakeLine = reconstruct(FunDef, FunName), + FdStr = "fd("++ FunName1 ++ ", " ++ FakeLine ++ ", " ++ max_consecutive_quotes(FunDef1) ++ ").", + {done, {ok, FakeResult, _}, _} = + erl_scan:tokens( + [], FdStr ++ "\n", + {1,1}, [text,{reserved_word_fun,fun erl_scan:reserved_word/1}]), erl_eval:extended_parse_exprs(FakeResult); _ -> erl_eval:extended_parse_exprs(Toks) end; @@ -486,28 +489,24 @@ get_command(Prompt, Eval, Bs, RT, FT, Ds) -> end, Pid = spawn_link(Parse), get_command1(Pid, Eval, Bs, RT, FT, Ds). -escape_quotes(String) -> escape_quotes(String, []). - -escape_quotes([], Acc) -> - % When we've processed all characters, reverse the accumulator - % because we've been prepending for efficiency reasons. - lists:reverse(Acc); - -escape_quotes([$\\, $\" | Rest], Acc) -> - % If we find an escaped quote (\"), - % we escape the backslash and the quote (\\\") and continue. - escape_quotes(Rest, [$\", $\\, $\\, $\\ | Acc]); - -escape_quotes([$\" | Rest], Acc) -> - % If we find a quote ("), - % we escape it (\\") and continue. - escape_quotes(Rest, [$\", $\\ | Acc]); - -escape_quotes([Char | Rest], Acc) -> - % In case of any other character, we keep it as is. - escape_quotes(Rest, [Char | Acc]). + + +max_consecutive_quotes(String) -> + Max = lists:max([2 | max_consecutive_quotes(String, 0)]), + Quotes = lists:duplicate(Max + 1, $"), + "~S" ++ Quotes ++ "\n" ++ String ++ "\n" ++ Quotes. + +max_consecutive_quotes([], Max) -> + [Max]; +max_consecutive_quotes([$\" | Rest], Max) -> + max_consecutive_quotes(Rest, Max + 1); +max_consecutive_quotes([_Char | Rest], 0 = Max) -> + max_consecutive_quotes(Rest, Max); +max_consecutive_quotes(Rest, Max) -> + [Max | max_consecutive_quotes(Rest, 0)]. + reconstruct(Fun, Name) -> - lists:flatten(erl_pp:expr(reconstruct1(Fun, Name))). + lists:flatten(erl_pp:expr(reconstruct1(Fun, Name), enc())). reconstruct1({function, Anno, Name, Arity, Clauses}, Name) -> {named_fun, Anno, 'RecursiveFuncVar', reconstruct1(Clauses, Name, Arity)}. reconstruct1([{call, Anno, {atom, Anno1, Name}, Args}|Body], Name, Arity) when length(Args) =:= Arity -> @@ -1466,7 +1465,7 @@ local_func(rd, [_], _Bs, _Shell, _RT, _FT, _Lf, _Ef) -> erlang:raise(error, function_clause, [{shell, rd, 1}]); local_func(rd, [{atom,_,RecName0},RecDef0], Bs, _Shell, RT, FT, _Lf, _Ef) -> RecDef = expand_value(RecDef0), - RDs = lists:flatten(erl_pp:expr(RecDef)), + RDs = lists:flatten(erl_pp:expr(RecDef, enc())), RecName = io_lib:write_atom_as_latin1(RecName0), Attr = lists:concat(["-record(", RecName, ",", RDs, ")."]), {ok, Tokens, _} = erl_scan:string(Attr), @@ -1578,7 +1577,7 @@ local_types(FT) -> local_records(FT) -> [list_to_binary(RecDef)||{{record_def, _},RecDef} <- ets:tab2list(FT)]. all_records(RT) -> - [list_to_binary(erl_pp:attribute(RecDef) ++ "\n")||{ _,RecDef} <- ets:tab2list(RT)]. + [list_to_binary(erl_pp:attribute(RecDef, enc()) ++ "\n")||{ _,RecDef} <- ets:tab2list(RT)]. write_and_compile_module(PathToFile, Output) -> case file:write_file(PathToFile, unicode:characters_to_binary(Output)) of ok -> c:c(PathToFile); @@ -2148,11 +2147,11 @@ erl_pp_format_func(String) -> {done, {ok, Toks, _}, _} -> try case erl_parse:parse_form(Toks) of - {ok, Def} -> lists:flatten(erl_pp:form(Def)) + {ok, Def} -> lists:flatten(erl_pp:form(Def, enc())) end catch _:_ -> case erl_parse:parse_exprs(Toks) of - {ok, Def1} -> lists:flatten(erl_pp:exprs(Def1))++"."; + {ok, Def1} -> lists:flatten(erl_pp:exprs(Def1, enc()))++"."; _ -> String end end; diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl index 8fc1fd4d17a7..52c419b280ff 100644 --- a/lib/stdlib/test/shell_SUITE.erl +++ b/lib/stdlib/test/shell_SUITE.erl @@ -777,6 +777,24 @@ local_definitions_save_to_module_and_forget(Config) when is_list(Config) -> "lf().\n" "lt().\n" "lr().\n">>), + + %% Test that local functions handle " and \ correctly when using lf + "ok.\nmy_func(X) ->\n \"\\\"\" ++ X ++ \"\\\"\".\n.\nok.\n" = t( + ~B""" + my_func(X) -> + "\"" ++ X ++ "\"". + lf(). + """), + + %% Test that local functions handle """ and \ correctly when using lf + "ok.\nmy_func(X) ->\n <<\"ABC😊\"/utf8>>.\n.\nok.\n" = t({ + ~B"""""" + my_func(X) -> + ~B"""" + ABC😊 + """". + lf(). + """""", unicode}), ok. %% Known bugs. @@ -3465,8 +3483,14 @@ t(L) -> t0({Bin,Enc}, F) -> %% Spawn a process so that io_request messages do not interfer. P = self(), - C = spawn(fun() -> t1(P, {Bin, Enc}, F) end), - receive {C, R} -> R end. + {C,M} = spawn_monitor(fun() -> t1(P, {Bin, Enc}, F) end), + receive + {C, R} -> + erlang:demonitor(M, [flush]), + R; + {'DOWN', _, _, _, _} = D -> + ct:fail({crashed, D}) + end. t1(Parent, {Bin,Enc}, F) -> io:format("*** Testing ~s~n", [binary_to_list(Bin)]),