├── Emakefile ├── Makefile ├── README.md ├── docs ├── rsz_1pretty-eshell-02.png └── rsz_pretty-eshell-1.png ├── ebin └── 3.4 │ ├── io_lib_pretty.beam │ ├── lib.beam │ ├── shell.beam │ └── shell_profile.beam ├── install.escript └── src └── 3.4 ├── io_lib_pretty.erl ├── lib.erl ├── shell.erl └── shell_profile.erl /Emakefile: -------------------------------------------------------------------------------- 1 | {["src/3.4/*"], [debug_info, {outdir, "ebin/3.4"}]}. % stdlib-3.4 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: compile 2 | 3 | compile: 4 | erl -make 5 | 6 | install: compile 7 | ./install.escript 8 | 9 | clean: 10 | rm ebin/*/*.beam 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pretty Eshell 2 | =============== 3 | Pretty Eshell - A Renovate Erlang Shell with Colorfull Syntax Highlight. 4 | 5 | ## Screenshots 6 | 7 | Takes on Ubuntu with Default Terminal. 8 | 9 | ![Pretty Eshell](https://github.com/spawnfest/ErlShell/blob/master/docs/rsz_pretty-eshell-1.png) 10 | 11 | ![Pretty Eshell](https://github.com/spawnfest/ErlShell/blob/master/docs/rsz_1pretty-eshell-02.png) 12 | 13 | ## Dependencies 14 | 15 | * Erlang OTP 20 (stdlib-3.4) 16 | 17 | ## How to install ## 18 | 19 | Clone: 20 | ~~~ 21 | $ git clone --recursive https://github.com/spawnfest/ErlShell.git 22 | ~~~ 23 | Automatically (you may need to run this command with `sudo` and run erl shell with `sudo`): 24 | 25 | `$ make install` 26 | 27 | Manually: 28 | 29 | 1. Find out what version of the stdlib library you're using by using `application:which_applications()` in the Erlang shell. The version number is the last element of the tuple. 30 | 2. Compile the files (`erl -make`). 31 | 3. Take the `.beam` files in `ebin/$VSN/` and move them to `$ROOT/lib/stdlib-$VSN/ebin/` for the OTP release of your choice. 32 | 4. Start the Erlang shell associated with this version of the Erlang/OTP. 33 | 5. Enjoy Colourfull shell :) 34 | 35 | ## How to revert-back ## 36 | 37 | Manually : 38 | 39 | 1. got to `$ROOT/lib/stdlib-$VSN/ebin/` and remove `io_lib_pretty.beam`,`lib.beam`,`shell_profile.beam` and `shell.beam`. 40 | 2. Backup file will be there with extension `.backup-pre-pretty-erlshell`. like `io_lib_pretty.beam.backup-pre-pretty-erlshell`, `lib.beam.backup-pre-pretty-erlshell` and so on. 41 | 3. Remove Back file extension ie. `.backup-pre-pretty-erlshell` 42 | 4. restart Erlang Shell. 43 | 5. done :) 44 | 45 | ## Author ## 46 | 47 | - Prakash Parmar ( prakash.parmar@outlook.com ) 48 | 49 | ## Thanks ## 50 | 51 | Thanks to Fred Hebert for details explanation about erlang shell on his blog post ![REPL? A bit more (and less) than that](https://ferd.ca/repl-a-bit-more-and-less-than-that.html) 52 | 53 | and last but not least A Big thanks to Erlang OTP team, SpawnFest 2017 Organizers, Judges, Sponsors and Gogole. 54 | 55 | 56 | # Happy Coding :) 57 | -------------------------------------------------------------------------------- /docs/rsz_1pretty-eshell-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawnfest/ErlShell/fa031514e533ed603007d256adb396425f9189cf/docs/rsz_1pretty-eshell-02.png -------------------------------------------------------------------------------- /docs/rsz_pretty-eshell-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawnfest/ErlShell/fa031514e533ed603007d256adb396425f9189cf/docs/rsz_pretty-eshell-1.png -------------------------------------------------------------------------------- /ebin/3.4/io_lib_pretty.beam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawnfest/ErlShell/fa031514e533ed603007d256adb396425f9189cf/ebin/3.4/io_lib_pretty.beam -------------------------------------------------------------------------------- /ebin/3.4/lib.beam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawnfest/ErlShell/fa031514e533ed603007d256adb396425f9189cf/ebin/3.4/lib.beam -------------------------------------------------------------------------------- /ebin/3.4/shell.beam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawnfest/ErlShell/fa031514e533ed603007d256adb396425f9189cf/ebin/3.4/shell.beam -------------------------------------------------------------------------------- /ebin/3.4/shell_profile.beam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spawnfest/ErlShell/fa031514e533ed603007d256adb396425f9189cf/ebin/3.4/shell_profile.beam -------------------------------------------------------------------------------- /install.escript: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | -module(install). 4 | -export([main/0, main/1]). 5 | 6 | -define(BACKUP_SUFFIX, ".backup-pre-pretty-erlshell"). 7 | 8 | main() -> 9 | Path = io_lib:format("~s/ebin",[code:lib_dir(stdlib)]), 10 | Version = io_lib:format( 11 | "~s", 12 | [ element( 13 | 3, 14 | lists:keyfind(stdlib,1,application:which_applications()) 15 | ) 16 | ] 17 | ), 18 | main([Path, Version]). 19 | 20 | main([Path, Version]) -> 21 | io:format("Path = ~s~nVersion = ~s~n", [Path, Version]), 22 | update_modules(Path, Version); 23 | main(_) -> main(). 24 | 25 | 26 | copy_files(Wildcard, Destination, Modes) -> 27 | 28 | N = lists:foldl( 29 | fun(Source, Acc) -> 30 | Target = case filelib:is_dir(Destination) of 31 | true -> filename:join(Destination, filename:basename(Source)); 32 | false -> Destination 33 | end, 34 | io:format("~ts~n", [Source]), 35 | Acc + case file:copy(Source, {Target, Modes}) of 36 | {ok, _BytesCopied} -> 37 | {ok, FileInfo} = file:read_file_info(Source), 38 | ok = file:write_file_info(Target, FileInfo), 39 | 1; 40 | {error, Reason} -> 41 | io:format("~ts~n", [file:format_error(Reason)]), 42 | 0 43 | end 44 | end, 45 | 0, 46 | filelib:wildcard(Wildcard)), 47 | io:format("~p file(s) copied~n", [N]). 48 | 49 | update_modules(Path, App_name) -> 50 | 51 | Bin_dir = filename:join(["ebin", App_name]), 52 | 53 | Files = filelib:wildcard(Bin_dir++"/*.beam"), 54 | 55 | Fun = fun(File_abs) -> 56 | 57 | File_rel = File_abs -- (Bin_dir++"/"), 58 | 59 | Backup = lists:flatten(io_lib:format("~s/" ++ File_rel ++ ?BACKUP_SUFFIX, [Path])), 60 | 61 | case filelib:is_regular(Backup) of 62 | true -> 63 | io:format("Backup of ~p exists~n",[File_rel]), 64 | ok; 65 | false -> 66 | io:format("Backing up existing file~n"), 67 | copy_files( 68 | filename:join(Path, File_rel), 69 | filename:join(Path, File_rel ++ ?BACKUP_SUFFIX), [exclusive]) 70 | end 71 | end, 72 | lists:foreach(Fun, Files), 73 | 74 | io:format("Installing...~n"), 75 | copy_files( Bin_dir++"/*.beam", Path, []). 76 | 77 | -------------------------------------------------------------------------------- /src/3.4/io_lib_pretty.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1996-2017. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | -module(io_lib_pretty). 21 | 22 | %%% Pretty printing Erlang terms 23 | %%% 24 | %%% In this module "print" means the formatted printing while "write" 25 | %%% means just writing out onto one line. 26 | 27 | -export([print/1,print/2,print/3,print/4,print/5,print/6]). 28 | 29 | -import(shell_profile,[beautify/2]). 30 | 31 | %%% 32 | %%% Exported functions 33 | %%% 34 | 35 | %% print(Term) -> [Chars] 36 | %% print(Term, Column, LineLength, Depth) -> [Chars] 37 | %% Depth = -1 gives unlimited print depth. Use io_lib:write for atomic terms. 38 | 39 | -spec print(term()) -> io_lib:chars(). 40 | 41 | print(Term) -> 42 | print(Term, 1, 80, -1). 43 | 44 | %% print(Term, RecDefFun) -> [Chars] 45 | %% print(Term, Depth, RecDefFun) -> [Chars] 46 | %% RecDefFun = fun(Tag, NoFields) -> [FieldTag] | no 47 | %% Used by the shell for printing records and for Unicode. 48 | 49 | -type rec_print_fun() :: fun((Tag :: atom(), NFields :: non_neg_integer()) -> 50 | no | [FieldName :: atom()]). 51 | -type column() :: integer(). 52 | -type line_length() :: pos_integer(). 53 | -type depth() :: integer(). 54 | -type max_chars() :: integer(). 55 | 56 | -type chars() :: io_lib:chars(). 57 | -type option() :: {column, column()} 58 | | {line_length, line_length()} 59 | | {depth, depth()} 60 | | {max_chars, max_chars()} 61 | | {record_print_fun, rec_print_fun()} 62 | | {strings, boolean()} 63 | | {encoding, latin1 | utf8 | unicode}. 64 | -type options() :: [option()]. 65 | 66 | -spec print(term(), rec_print_fun()) -> chars(); 67 | (term(), options()) -> chars(). 68 | 69 | print(Term, Options) when is_list(Options) -> 70 | Col = get_option(column, Options, 1), 71 | Ll = get_option(line_length, Options, 80), 72 | D = get_option(depth, Options, -1), 73 | M = get_option(max_chars, Options, -1), 74 | RecDefFun = get_option(record_print_fun, Options, no_fun), 75 | Encoding = get_option(encoding, Options, epp:default_encoding()), 76 | Strings = get_option(strings, Options, true), 77 | print(Term, Col, Ll, D, M, RecDefFun, Encoding, Strings); 78 | print(Term, RecDefFun) -> 79 | print(Term, -1, RecDefFun). 80 | 81 | -spec print(term(), depth(), rec_print_fun()) -> chars(). 82 | 83 | print(Term, Depth, RecDefFun) -> 84 | print(Term, 1, 80, Depth, RecDefFun). 85 | 86 | -spec print(term(), column(), line_length(), depth()) -> chars(). 87 | 88 | print(Term, Col, Ll, D) -> 89 | print(Term, Col, Ll, D, _M=-1, no_fun, latin1, true). 90 | 91 | -spec print(term(), column(), line_length(), depth(), rec_print_fun()) -> 92 | chars(). 93 | print(Term, Col, Ll, D, RecDefFun) -> 94 | print(Term, Col, Ll, D, _M=-1, RecDefFun). 95 | 96 | -spec print(term(), column(), line_length(), depth(), max_chars(), 97 | rec_print_fun()) -> chars(). 98 | 99 | print(Term, Col, Ll, D, M, RecDefFun) -> 100 | print(Term, Col, Ll, D, M, RecDefFun, latin1, true). 101 | 102 | %% D = Depth, default -1 (infinite), or LINEMAX=30 when printing from shell 103 | %% Col = current column, default 1 104 | %% Ll = line length/~p field width, default 80 105 | %% M = CHAR_MAX (-1 if no max, 60 when printing from shell) 106 | 107 | print(_, _, _, 0, _M, _RF, _Enc, _Str) -> beautify(ellipsis,"..."); 108 | print(Term, Col, Ll, D, M, RecDefFun, Enc, Str) when Col =< 0 -> 109 | %% ensure Col is at least 1 110 | print(Term, 1, Ll, D, M, RecDefFun, Enc, Str); 111 | print(Atom, _Col, _Ll, _D, _M, _RF, Enc, _Str) when is_atom(Atom) -> 112 | write_atom(Atom, Enc); 113 | print(Term, Col, Ll, D, M0, RecDefFun, Enc, Str) when is_tuple(Term); 114 | is_list(Term); 115 | is_map(Term); 116 | is_bitstring(Term) -> 117 | %% preprocess and compute total number of chars 118 | If = {_S, Len} = print_length(Term, D, RecDefFun, Enc, Str), 119 | %% use Len as CHAR_MAX if M0 = -1 120 | M = max_cs(M0, Len), 121 | if 122 | Len < Ll - Col, Len =< M -> 123 | %% write the whole thing on a single line when there is room 124 | write(If); 125 | true -> 126 | %% compute the indentation TInd for tagged tuples and records 127 | TInd = while_fail([-1, 4], 128 | fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end, 129 | 1), 130 | pp(If, Col, Ll, M, TInd, indent(Col), 0, 0) 131 | end; 132 | print(Term, _Col, _Ll, _D, _M, _RF, _Enc, _Str) -> 133 | %% atomic data types (bignums, atoms, ...) are never truncated 134 | Str = io_lib:write(Term), 135 | beautify(term,Str). 136 | 137 | %%% 138 | %%% Local functions 139 | %%% 140 | 141 | %% use M only if nonnegative, otherwise use Len as default value 142 | max_cs(M, Len) when M < 0 -> 143 | Len; 144 | max_cs(M, _Len) -> 145 | M. 146 | 147 | -define(ATM(T), is_list(element(1, T))). 148 | -define(ATM_PAIR(Pair), 149 | ?ATM(element(2, element(1, Pair))) % Key 150 | andalso 151 | ?ATM(element(3, element(1, Pair)))). % Value 152 | -define(ATM_FLD(Field), ?ATM(element(4, element(1, Field)))). 153 | 154 | pp({_S, Len} = If, Col, Ll, M, _TInd, _Ind, LD, W) 155 | when Len < Ll - Col - LD, Len + W + LD =< M -> 156 | write(If); 157 | 158 | pp({{list,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) -> 159 | [ beautify(sb,"["), pp_list(L, Col + 1, Ll, M, TInd, indent(1, Ind), LD, beautify(pipe,"|"), W + 1), 160 | beautify(sb,"]")]; 161 | 162 | pp({{tuple,true,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) -> 163 | [ beautify(cb,"{"), pp_tag_tuple(L, Col, Ll, M, TInd, Ind, LD, W + 1), beautify(cb,"}")]; 164 | 165 | pp({{tuple,false,L}, _Len}, Col, Ll, M, TInd, Ind, LD, W) -> 166 | [ beautify(cb, "{"), pp_list(L, Col + 1, Ll, M, TInd, indent(1, Ind), LD, beautify(comma,","), W + 1), 167 | beautify(cb,"}")]; 168 | 169 | pp({{map,Pairs},_Len}, Col, Ll, M, TInd, Ind, LD, W) -> 170 | [ beautify(hash,"#"), beautify(cb,"{"), pp_map(Pairs, Col + 2, Ll, M, TInd, indent(2, Ind), LD, W + 1), 171 | beautify(cb,"}")]; 172 | 173 | pp({{record,[{Name,NLen} | L]}, _Len}, Col, Ll, M, TInd, Ind, LD, W) -> 174 | [Name, beautify(cb, "{"), pp_record(L, NLen, Col, Ll, M, TInd, Ind, LD, W + NLen+1), beautify(cb,"}")]; 175 | 176 | pp({{bin,S}, _Len}, Col, Ll, M, _TInd, Ind, LD, W) -> 177 | pp_binary(S, Col + 2, Ll, M, indent(2, Ind), LD, W); 178 | 179 | pp({S, _Len}, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 180 | S. 181 | 182 | %% Print a tagged tuple by indenting the rest of the elements 183 | %% differently to the tag. Tuple has size >= 2. 184 | pp_tag_tuple([{Tag,Tlen} | L], Col, Ll, M, TInd, Ind, LD, W) -> 185 | %% this uses TInd 186 | TagInd = Tlen + 2, 187 | Tcol = Col + TagInd, 188 | S = beautify(comma,","), 189 | if 190 | TInd > 0, TagInd > TInd -> 191 | Col1 = Col + TInd, 192 | Indent = indent(TInd, Ind), 193 | [Tag|pp_tail(L, Col1, Tcol, Ll, M, TInd, Indent, LD, S, W+Tlen)]; 194 | true -> 195 | Indent = indent(TagInd, Ind), 196 | [Tag, S | pp_list(L, Tcol, Ll, M, TInd, Indent, LD, S, W+Tlen+1)] 197 | end. 198 | 199 | pp_map([], _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 200 | ""; 201 | pp_map({dots, _}, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 202 | beautify(ellipsis,"..."); 203 | pp_map([P | Ps], Col, Ll, M, TInd, Ind, LD, W) -> 204 | {PS, PW} = pp_pair(P, Col, Ll, M, TInd, Ind, last_depth(Ps, LD), W), 205 | [PS | pp_pairs_tail(Ps, Col, Col + PW, Ll, M, TInd, Ind, LD, PW)]. 206 | 207 | pp_pairs_tail([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 208 | ""; 209 | pp_pairs_tail({dots, _}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _LD, _W) -> 210 | lists:concat([beautify(comma,","),beautify(ellipsis,"...")]); 211 | 212 | pp_pairs_tail([{_, Len}=P | Ps], Col0, Col, Ll, M, TInd, Ind, LD, W) -> 213 | LD1 = last_depth(Ps, LD), 214 | ELen = 1 + Len, 215 | if 216 | LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_PAIR(P); 217 | LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_PAIR(P) -> 218 | [beautify(comma,","), write_pair(P) | 219 | pp_pairs_tail(Ps, Col0, Col+ELen, Ll, M, TInd, Ind, LD, W+ELen)]; 220 | true -> 221 | {PS, PW} = pp_pair(P, Col0, Ll, M, TInd, Ind, LD1, 0), 222 | [beautify(comma,","), $\n, Ind, PS | 223 | pp_pairs_tail(Ps, Col0, Col0 + PW, Ll, M, TInd, Ind, LD, PW)] 224 | end. 225 | 226 | pp_pair({_, Len}=Pair, Col, Ll, M, _TInd, _Ind, LD, W) 227 | when Len < Ll - Col - LD, Len + W + LD =< M -> 228 | {write_pair(Pair), if 229 | ?ATM_PAIR(Pair) -> 230 | Len; 231 | true -> 232 | Ll % force nl 233 | end}; 234 | pp_pair({{map_pair, K, V}, _Len}, Col0, Ll, M, TInd, Ind0, LD, W) -> 235 | I = map_value_indent(TInd), 236 | Ind = indent(I, Ind0), 237 | {[pp(K, Col0, Ll, M, TInd, Ind0, LD, W), beautify(arrow," =>\n"), 238 | Ind | pp(V, Col0 + I, Ll, M, TInd, Ind, LD, 0)], Ll}. % force nl 239 | 240 | pp_record([], _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 241 | ""; 242 | 243 | pp_record({dots, _}, _Nlen, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 244 | beautify(ellipsis,"..."); 245 | 246 | pp_record([F | Fs], Nlen, Col0, Ll, M, TInd, Ind0, LD, W0) -> 247 | Nind = Nlen + 1, 248 | {Col, Ind, S, W} = rec_indent(Nind, TInd, Col0, Ind0, W0), 249 | {FS, FW} = pp_field(F, Col, Ll, M, TInd, Ind, last_depth(Fs, LD), W), 250 | [S, FS | pp_fields_tail(Fs, Col, Col + FW, Ll, M, TInd, Ind, LD, W + FW)]. 251 | 252 | pp_fields_tail([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) -> 253 | ""; 254 | 255 | pp_fields_tail({dots, _}, _Col0, _Col, _M, _Ll, _TInd, _Ind, _LD, _W) -> 256 | lists:concat([beautify(comma,","),beautify(ellipsis,"...")]); 257 | 258 | pp_fields_tail([{_, Len}=F | Fs], Col0, Col, Ll, M, TInd, Ind, LD, W) -> 259 | LD1 = last_depth(Fs, LD), 260 | ELen = 1 + Len, 261 | if 262 | LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_FLD(F); 263 | LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_FLD(F) -> 264 | [beautify(comma,","), write_field(F) | 265 | pp_fields_tail(Fs, Col0, Col+ELen, Ll, M, TInd, Ind, LD, W+ELen)]; 266 | true -> 267 | {FS, FW} = pp_field(F, Col0, Ll, M, TInd, Ind, LD1, 0), 268 | [beautify(comma,","), $\n, Ind, FS | 269 | pp_fields_tail(Fs, Col0, Col0 + FW, Ll, M, TInd, Ind, LD, FW)] 270 | end. 271 | 272 | pp_field({_, Len}=Fl, Col, Ll, M, _TInd, _Ind, LD, W) 273 | when Len < Ll - Col - LD, Len + W + LD =< M -> 274 | {write_field(Fl), if 275 | ?ATM_FLD(Fl) -> 276 | Len; 277 | true -> 278 | Ll % force nl 279 | end}; 280 | pp_field({{field, Name, NameL, F}, _Len}, Col0, Ll, M, TInd, Ind0, LD, W0) -> 281 | {Col, Ind, S, W} = rec_indent(NameL, TInd, Col0, Ind0, W0 + NameL), 282 | Sep = case S of 283 | [$\n | _] -> " ="; 284 | _ -> " = " 285 | end, 286 | {[beautify(name,Name), beautify(eq,Sep), S | pp(F, Col, Ll, M, TInd, Ind, LD, W)], Ll}. % force nl 287 | 288 | rec_indent(RInd, TInd, Col0, Ind0, W0) -> 289 | %% this uses TInd 290 | Nl = (TInd > 0) and (RInd > TInd), 291 | DCol = case Nl of 292 | true -> TInd; 293 | false -> RInd 294 | end, 295 | Col = Col0 + DCol, 296 | Ind = indent(DCol, Ind0), 297 | S = case Nl of 298 | true -> [$\n | Ind]; 299 | false -> "" 300 | end, 301 | W = case Nl of 302 | true -> 0; 303 | false -> W0 304 | end, 305 | {Col, Ind, S, W}. 306 | 307 | pp_list({dots, _}, _Col0, _Ll, _M, _TInd, _Ind, _LD, _S, _W) -> 308 | beautify(ellipsis,"..."); 309 | 310 | pp_list([E | Es], Col0, Ll, M, TInd, Ind, LD, S, W) -> 311 | {ES, WE} = pp_element(E, Col0, Ll, M, TInd, Ind, last_depth(Es, LD), W), 312 | [ES | pp_tail(Es, Col0, Col0 + WE, Ll, M, TInd, Ind, LD, S, W + WE)]. 313 | 314 | pp_tail([], _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, _S, _W) -> 315 | ""; 316 | pp_tail([{_, Len}=E | Es], Col0, Col, Ll, M, TInd, Ind, LD, S, W) -> 317 | LD1 = last_depth(Es, LD), 318 | ELen = 1 + Len, 319 | if 320 | LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM(E); 321 | LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM(E) -> 322 | [ beautify(comma,","), write(E) | 323 | pp_tail(Es, Col0, Col + ELen, Ll, M, TInd, Ind, LD, S, W+ELen)]; 324 | true -> 325 | {ES, WE} = pp_element(E, Col0, Ll, M, TInd, Ind, LD1, 0), 326 | [beautify(comma,","), $\n, Ind, ES | 327 | pp_tail(Es, Col0, Col0 + WE, Ll, M, TInd, Ind, LD, S, WE)] 328 | end; 329 | pp_tail({dots, _}, _Col0, _Col, _Ll, _M, _TInd, _Ind, _LD, S, _W) -> 330 | [S | beautify(ellipsis,"...")]; 331 | pp_tail({_, Len}=E, _Col0, Col, Ll, M, _TInd, _Ind, LD, S, W) 332 | when Len + 1 < Ll - Col - (LD + 1), 333 | Len + 1 + W + (LD + 1) =< M, 334 | ?ATM(E) -> 335 | [S | write(E)]; 336 | pp_tail(E, Col0, _Col, Ll, M, TInd, Ind, LD, S, _W) -> 337 | [S, $\n, Ind | pp(E, Col0, Ll, M, TInd, Ind, LD + 1, 0)]. 338 | 339 | pp_element({_, Len}=E, Col, Ll, M, _TInd, _Ind, LD, W) 340 | when Len < Ll - Col - LD, Len + W + LD =< M, ?ATM(E) -> 341 | {write(E), Len}; 342 | pp_element(E, Col, Ll, M, TInd, Ind, LD, W) -> 343 | {pp(E, Col, Ll, M, TInd, Ind, LD, W), Ll}. % force nl 344 | 345 | %% Reuse the list created by io_lib:write_binary()... 346 | pp_binary([LT,LT,S,GT,GT], Col, Ll, M, Ind, LD, W) -> 347 | N = erlang:max(8, erlang:min(Ll - Col, M - 4 - W) - LD), 348 | [ beautify(bstr,"<<"),pp_binary(S, N, N, Ind), beautify(bstr,">>")]. 349 | 350 | pp_binary([BS,$, | S], N, N0, Ind) -> 351 | Len = length(BS) + 1, 352 | case N - Len of 353 | N1 when N1 < 0 -> 354 | [$\n, Ind, beautify(digits,BS), beautify(comma,",") | pp_binary(S, N0 - Len, N0, Ind)]; 355 | N1 -> 356 | [beautify(digits,BS), beautify(comma,",") | pp_binary(S, N1, N0, Ind)] 357 | end; 358 | pp_binary([BS1, $:, BS2]=S, N, _N0, Ind) 359 | when length(BS1) + length(BS2) + 1 > N -> 360 | [$\n, Ind, S]; 361 | pp_binary(S, N, _N0, Ind) -> 362 | case iolist_size(S) > N of 363 | true -> 364 | [$\n, Ind, S]; 365 | false -> 366 | S 367 | end. 368 | 369 | %% write the whole thing on a single line 370 | write({{tuple, _IsTagged, L}, _}) -> 371 | [beautify(cb,"{"), write_list(L, beautify(comma,",")), beautify(cb,"}")]; 372 | 373 | write({{list, L}, _}) -> 374 | [beautify(sb,"["), write_list(L, beautify(pipe,"|")), beautify(sb,"]")]; 375 | 376 | write({{map, Pairs}, _}) -> 377 | [beautify(hash,"#"),beautify(cb,"{"), write_list(Pairs, beautify(comma,",")), beautify(cb,"}")]; 378 | 379 | write({{map_pair, _K, _V}, _}=Pair) -> 380 | write_pair(Pair); 381 | 382 | write({{record, [{Name,_} | L]}, _}) -> 383 | [Name, beautify(cb,"{"), write_fields(L), beautify(cb,"}")]; 384 | 385 | write({{bin, S}, _}) -> 386 | S; 387 | write({S, _}) -> 388 | S. 389 | 390 | write_pair({{map_pair, K, V}, _}) -> 391 | [write(K), beautify(arrow," => "), write(V)]. 392 | 393 | write_fields([]) -> 394 | ""; 395 | write_fields({dots, _}) -> 396 | beautify(ellipsis,"..."); 397 | write_fields([F | Fs]) -> 398 | [write_field(F) | write_fields_tail(Fs)]. 399 | 400 | write_fields_tail([]) -> 401 | ""; 402 | write_fields_tail({dots, _}) -> 403 | lists:concat([beautify(comma,","),beautify(ellipsis,"...")]); 404 | write_fields_tail([F | Fs]) -> 405 | [beautify(comma,","), write_field(F) | write_fields_tail(Fs)]. 406 | 407 | write_field({{field, Name, _NameL, F}, _}) -> 408 | [beautify(name,Name), beautify(eq," = ") | write(F)]. 409 | 410 | write_list({dots, _}, _S) -> 411 | beautify(ellipsis,"..."); 412 | write_list([E | Es], S) -> 413 | [write(E) | write_tail(Es, S)]. 414 | 415 | write_tail([], _S) -> 416 | []; 417 | write_tail([E | Es], S) -> 418 | [beautify(comma,","), write(E) | write_tail(Es, S)]; 419 | write_tail({dots, _}, S) -> 420 | [S | beautify(ellipsis,"...")]; 421 | write_tail(E, S) -> 422 | [S | write(E)]. 423 | 424 | %% The depth (D) is used for extracting and counting the characters to 425 | %% print. The structure is kept so that the returned intermediate 426 | %% format can be formatted. The separators (list, tuple, record, map) are 427 | %% counted but need to be added later. 428 | 429 | %% D =/= 0 430 | print_length([], _D, _RF, _Enc, _Str) -> 431 | {beautify(sb,"[]"), 2}; 432 | print_length({}, _D, _RF, _Enc, _Str) -> 433 | {beautify(cb,"{}"), 2}; 434 | print_length(#{}=M, _D, _RF, _Enc, _Str) when map_size(M) =:= 0 -> 435 | {lists:concat([beautify(hash,"#"),beautify(cb,"{}")]), 3}; 436 | print_length(Atom, _D, _RF, Enc, _Str) when is_atom(Atom) -> 437 | S = write_atom(Atom, Enc), 438 | Str = beautify(term,S), 439 | {Str, lists:flatlength(S)}; 440 | print_length(List, D, RF, Enc, Str) when is_list(List) -> 441 | %% only flat lists are "printable" 442 | case Str andalso printable_list(List, D, Enc) of 443 | true -> 444 | %% print as string, escaping double-quotes in the list 445 | S = write_string(List, Enc), 446 | {S, length(S)}; 447 | %% Truncated lists could break some existing code. 448 | % {true, Prefix} -> 449 | % S = write_string(Prefix, Enc), 450 | % {[S | "..."], 3 + length(S)}; 451 | false -> 452 | print_length_list(List, D, RF, Enc, Str) 453 | end; 454 | print_length(Fun, _D, _RF, _Enc, _Str) when is_function(Fun) -> 455 | S = io_lib:write(Fun), 456 | Str = beautify(function, S), 457 | {Str, iolist_size(S)}; 458 | print_length(R, D, RF, Enc, Str) when is_atom(element(1, R)), 459 | is_function(RF) -> 460 | case RF(element(1, R), tuple_size(R) - 1) of 461 | no -> 462 | print_length_tuple(R, D, RF, Enc, Str); 463 | RDefs -> 464 | print_length_record(R, D, RF, RDefs, Enc, Str) 465 | end; 466 | print_length(Tuple, D, RF, Enc, Str) when is_tuple(Tuple) -> 467 | print_length_tuple(Tuple, D, RF, Enc, Str); 468 | 469 | print_length(Map, D, RF, Enc, Str) when is_map(Map) -> 470 | print_length_map(Map, D, RF, Enc, Str); 471 | 472 | print_length(<<>>, _D, _RF, _Enc, _Str) -> 473 | {beautify(bstr,"<<>>"), 4}; 474 | 475 | print_length(<<_/bitstring>>, 1, _RF, _Enc, _Str) -> 476 | {lists:concat([beautify(bstr,"<<"),beautify(ellipsis,"..."),beautify(bstr,">>")]), 7}; 477 | 478 | print_length(<<_/bitstring>>=Bin, D, _RF, Enc, Str) -> 479 | case bit_size(Bin) rem 8 of 480 | 0 -> 481 | D1 = D - 1, 482 | case Str andalso printable_bin(Bin, D1, Enc) of 483 | {true, List} when is_list(List) -> 484 | S = io_lib:write_string(List, $"), %" 485 | {[ beautify(bstr,"<<"),beautify(field,S),beautify(bstr,">>")], 4 + length(S)}; 486 | 487 | {false, List} when is_list(List) -> 488 | S = io_lib:write_string(List, $"), %" 489 | {[beautify(bstr,"<<"),beautify(field,S),beautify(bstr,"/utf8>>")], 9 + length(S)}; 490 | 491 | {true, true, Prefix} -> 492 | S = io_lib:write_string(Prefix, $"), %" 493 | {[beautify(bstr,"<<"),beautify(field,S) | beautify(ellipsis,"...")++beautify(bstr,">>")], 7 + length(S)}; 494 | 495 | {false, true, Prefix} -> 496 | S = io_lib:write_string(Prefix, $"), %" 497 | {[beautify(bstr,"<<"),beautify(field,S) | beautify(bstr,"/utf8...>>")], 12 + length(S)}; 498 | false -> 499 | S = io_lib:write(Bin, D), 500 | {{bin,S}, iolist_size(S)} 501 | end; 502 | _ -> 503 | S = io_lib:write(Bin, D), 504 | {{bin,S}, iolist_size(S)} 505 | end; 506 | 507 | print_length(Term, _D, _RF, _Enc, _Str) -> 508 | S = io_lib:write(Term), 509 | Str = beautify(term,S), 510 | {Str, lists:flatlength(Str)}. 511 | 512 | print_length_map(_Map, 1, _RF, _Enc, _Str) -> 513 | { lists:concat([beautify(hash,"#"), 514 | beautify(cb, "{"), 515 | beautify(ellipsis, "..."), 516 | beautify(cb,"}")]), 6}; 517 | 518 | print_length_map(Map, D, RF, Enc, Str) when is_map(Map) -> 519 | Pairs = print_length_map_pairs(erts_internal:maps_to_list(Map, D), D, RF, Enc, Str), 520 | {{map, Pairs}, list_length(Pairs, 3)}. 521 | 522 | print_length_map_pairs([], _D, _RF, _Enc, _Str) -> 523 | []; 524 | print_length_map_pairs(_Pairs, 1, _RF, _Enc, _Str) -> 525 | {dots, 3}; 526 | print_length_map_pairs([{K, V} | Pairs], D, RF, Enc, Str) -> 527 | [print_length_map_pair(K, V, D - 1, RF, Enc, Str) | 528 | print_length_map_pairs(Pairs, D - 1, RF, Enc, Str)]. 529 | 530 | print_length_map_pair(K, V, D, RF, Enc, Str) -> 531 | {KS, KL} = print_length(K, D, RF, Enc, Str), 532 | {VS, VL} = print_length(V, D, RF, Enc, Str), 533 | KL1 = KL + 4, 534 | {{map_pair, {KS, KL1}, {VS, VL}}, KL1 + VL}. 535 | 536 | print_length_tuple(_Tuple, 1, _RF, _Enc, _Str) -> 537 | {lists:concat([ beautify(cb, "{"), 538 | beautify(ellipsis, "..."), 539 | beautify(cb,"}")]), 5}; 540 | print_length_tuple(Tuple, D, RF, Enc, Str) -> 541 | L = print_length_list1(tuple_to_list(Tuple), D, RF, Enc, Str), 542 | IsTagged = is_atom(element(1, Tuple)) and (tuple_size(Tuple) > 1), 543 | {{tuple,IsTagged,L}, list_length(L, 2)}. 544 | 545 | print_length_record(_Tuple, 1, _RF, _RDefs, _Enc, _Str) -> 546 | {lists:concat([beautify(cb, "{"), 547 | beautify(ellipsis, "..."), 548 | beautify(cb,"}")]), 5}; 549 | print_length_record(Tuple, D, RF, RDefs, Enc, Str) -> 550 | Name = [$# | write_atom(element(1, Tuple), Enc)], 551 | NameL = length(Name), 552 | Elements = tl(tuple_to_list(Tuple)), 553 | L = print_length_fields(RDefs, D - 1, Elements, RF, Enc, Str), 554 | {{record, [{beautify(name,Name),NameL} | L]}, list_length(L, NameL + 2)}. 555 | 556 | print_length_fields([], _D, [], _RF, _Enc, _Str) -> 557 | []; 558 | print_length_fields(_, 1, _, _RF, _Enc, _Str) -> 559 | {dots, 3}; 560 | print_length_fields([Def | Defs], D, [E | Es], RF, Enc, Str) -> 561 | [print_length_field(Def, D - 1, E, RF, Enc, Str) | 562 | print_length_fields(Defs, D - 1, Es, RF, Enc, Str)]. 563 | 564 | print_length_field(Def, D, E, RF, Enc, Str) -> 565 | Name = write_atom(Def, Enc), 566 | {S, L} = print_length(E, D, RF, Enc, Str), 567 | NameL = length(Name) + 3, 568 | {{field, Name, NameL, {S, L}}, NameL + L}. 569 | 570 | print_length_list(List, D, RF, Enc, Str) -> 571 | L = print_length_list1(List, D, RF, Enc, Str), 572 | {{list, L}, list_length(L, 2)}. 573 | 574 | print_length_list1([], _D, _RF, _Enc, _Str) -> 575 | []; 576 | print_length_list1(_, 1, _RF, _Enc, _Str) -> 577 | {dots, 3}; 578 | print_length_list1([E | Es], D, RF, Enc, Str) -> 579 | [print_length(E, D - 1, RF, Enc, Str) | 580 | print_length_list1(Es, D - 1, RF, Enc, Str)]; 581 | print_length_list1(E, D, RF, Enc, Str) -> 582 | print_length(E, D - 1, RF, Enc, Str). 583 | 584 | list_length([], Acc) -> 585 | Acc; 586 | list_length([{_, Len} | Es], Acc) -> 587 | list_length_tail(Es, Acc + Len); 588 | list_length({_, Len}, Acc) -> 589 | Acc + Len. 590 | 591 | list_length_tail([], Acc) -> 592 | Acc; 593 | list_length_tail([{_,Len} | Es], Acc) -> 594 | list_length_tail(Es, Acc + 1 + Len); 595 | list_length_tail({_, Len}, Acc) -> 596 | Acc + 1 + Len. 597 | 598 | %% ?CHARS printable characters has depth 1. 599 | -define(CHARS, 4). 600 | 601 | %% only flat lists are "printable" 602 | printable_list(_L, 1, _Enc) -> 603 | false; 604 | printable_list(L, _D, latin1) -> 605 | io_lib:printable_latin1_list(L); 606 | printable_list(L, _D, _Uni) -> 607 | io_lib:printable_list(L). 608 | 609 | printable_bin(Bin, D, Enc) when D >= 0, ?CHARS * D =< byte_size(Bin) -> 610 | printable_bin(Bin, erlang:min(?CHARS * D, byte_size(Bin)), D, Enc); 611 | printable_bin(Bin, D, Enc) -> 612 | printable_bin(Bin, byte_size(Bin), D, Enc). 613 | 614 | printable_bin(Bin, Len, D, latin1) -> 615 | N = erlang:min(20, Len), 616 | L = binary_to_list(Bin, 1, N), 617 | case printable_latin1_list(L, N) of 618 | all when N =:= byte_size(Bin) -> 619 | {true, L}; 620 | all when N =:= Len -> % N < byte_size(Bin) 621 | {true, true, L}; 622 | all -> 623 | case printable_bin1(Bin, 1 + N, Len - N) of 624 | 0 when byte_size(Bin) =:= Len -> 625 | {true, binary_to_list(Bin)}; 626 | NC when D > 0, Len - NC >= D -> 627 | {true, true, binary_to_list(Bin, 1, Len - NC)}; 628 | NC when is_integer(NC) -> 629 | false 630 | end; 631 | NC when is_integer(NC), D > 0, N - NC >= D -> 632 | {true, true, binary_to_list(Bin, 1, N - NC)}; 633 | NC when is_integer(NC) -> 634 | false 635 | end; 636 | printable_bin(Bin, Len, D, _Uni) -> 637 | case valid_utf8(Bin,Len) of 638 | true -> 639 | case printable_unicode(Bin, Len, [], io:printable_range()) of 640 | {_, <<>>, L} -> 641 | {byte_size(Bin) =:= length(L), L}; 642 | {NC, Bin1, L} when D > 0, Len - NC >= D -> 643 | {byte_size(Bin)-byte_size(Bin1) =:= length(L), true, L}; 644 | {_NC, _Bin, _L} -> 645 | false 646 | end; 647 | false -> 648 | printable_bin(Bin, Len, D, latin1) 649 | end. 650 | 651 | printable_bin1(_Bin, _Start, 0) -> 652 | 0; 653 | printable_bin1(Bin, Start, Len) -> 654 | N = erlang:min(10000, Len), 655 | L = binary_to_list(Bin, Start, Start + N - 1), 656 | case printable_latin1_list(L, N) of 657 | all -> 658 | printable_bin1(Bin, Start + N, Len - N); 659 | NC when is_integer(NC) -> 660 | Len - (N - NC) 661 | end. 662 | 663 | %% -> all | integer() >=0. Adopted from io_lib.erl. 664 | % printable_latin1_list([_ | _], 0) -> 0; 665 | printable_latin1_list([C | Cs], N) when C >= $\s, C =< $~ -> 666 | printable_latin1_list(Cs, N - 1); 667 | printable_latin1_list([C | Cs], N) when C >= $\240, C =< $\377 -> 668 | printable_latin1_list(Cs, N - 1); 669 | printable_latin1_list([$\n | Cs], N) -> printable_latin1_list(Cs, N - 1); 670 | printable_latin1_list([$\r | Cs], N) -> printable_latin1_list(Cs, N - 1); 671 | printable_latin1_list([$\t | Cs], N) -> printable_latin1_list(Cs, N - 1); 672 | printable_latin1_list([$\v | Cs], N) -> printable_latin1_list(Cs, N - 1); 673 | printable_latin1_list([$\b | Cs], N) -> printable_latin1_list(Cs, N - 1); 674 | printable_latin1_list([$\f | Cs], N) -> printable_latin1_list(Cs, N - 1); 675 | printable_latin1_list([$\e | Cs], N) -> printable_latin1_list(Cs, N - 1); 676 | printable_latin1_list([], _) -> all; 677 | printable_latin1_list(_, N) -> N. 678 | 679 | valid_utf8(<<>>,_) -> 680 | true; 681 | valid_utf8(_,0) -> 682 | true; 683 | valid_utf8(<<_/utf8, R/binary>>,N) -> 684 | valid_utf8(R,N-1); 685 | valid_utf8(_,_) -> 686 | false. 687 | 688 | printable_unicode(<>=Bin, I, L, Range) when I > 0 -> 689 | case printable_char(C,Range) of 690 | true -> 691 | printable_unicode(R, I - 1, [C | L],Range); 692 | false -> 693 | {I, Bin, lists:reverse(L)} 694 | end; 695 | printable_unicode(Bin, I, L,_) -> 696 | {I, Bin, lists:reverse(L)}. 697 | 698 | printable_char($\n,_) -> true; 699 | printable_char($\r,_) -> true; 700 | printable_char($\t,_) -> true; 701 | printable_char($\v,_) -> true; 702 | printable_char($\b,_) -> true; 703 | printable_char($\f,_) -> true; 704 | printable_char($\e,_) -> true; 705 | printable_char(C,latin1) -> 706 | C >= $\s andalso C =< $~ orelse 707 | C >= 16#A0 andalso C =< 16#FF; 708 | printable_char(C,unicode) -> 709 | C >= $\s andalso C =< $~ orelse 710 | C >= 16#A0 andalso C < 16#D800 orelse 711 | C > 16#DFFF andalso C < 16#FFFE orelse 712 | C > 16#FFFF andalso C =< 16#10FFFF. 713 | 714 | write_atom(A, latin1) -> 715 | io_lib:write_atom_as_latin1(A); 716 | write_atom(A, _Uni) -> 717 | io_lib:write_atom(A). 718 | 719 | write_string(S, latin1) -> 720 | io_lib:write_latin1_string(S, $"); %" 721 | write_string(S, _Uni) -> 722 | io_lib:write_string(S, $"). %" 723 | 724 | %% Throw 'no_good' if the indentation exceeds half the line length 725 | %% unless there is room for M characters on the line. 726 | 727 | cind({_S, Len}, Col, Ll, M, Ind, LD, W) when Len < Ll - Col - LD, 728 | Len + W + LD =< M -> 729 | Ind; 730 | cind({{list,L}, _Len}, Col, Ll, M, Ind, LD, W) -> 731 | cind_list(L, Col + 1, Ll, M, Ind, LD, W + 1); 732 | cind({{tuple,true,L}, _Len}, Col, Ll, M, Ind, LD, W) -> 733 | cind_tag_tuple(L, Col, Ll, M, Ind, LD, W + 1); 734 | cind({{tuple,false,L}, _Len}, Col, Ll, M, Ind, LD, W) -> 735 | cind_list(L, Col + 1, Ll, M, Ind, LD, W + 1); 736 | cind({{map,Pairs},_Len}, Col, Ll, M, Ind, LD, W) -> 737 | cind_map(Pairs, Col + 2, Ll, M, Ind, LD, W + 2); 738 | cind({{record,[{_Name,NLen} | L]}, _Len}, Col, Ll, M, Ind, LD, W) -> 739 | cind_record(L, NLen, Col, Ll, M, Ind, LD, W + NLen + 1); 740 | cind({{bin,_S}, _Len}, _Col, _Ll, _M, Ind, _LD, _W) -> 741 | Ind; 742 | cind({_S, _Len}, _Col, _Ll, _M, Ind, _LD, _W) -> 743 | Ind. 744 | 745 | cind_tag_tuple([{_Tag,Tlen} | L], Col, Ll, M, Ind, LD, W) -> 746 | TagInd = Tlen + 2, 747 | Tcol = Col + TagInd, 748 | if 749 | Ind > 0, TagInd > Ind -> 750 | Col1 = Col + Ind, 751 | if 752 | M + Col1 =< Ll; Col1 =< Ll div 2 -> 753 | cind_tail(L, Col1, Tcol, Ll, M, Ind, LD, W + Tlen); 754 | true -> 755 | throw(no_good) 756 | end; 757 | M + Tcol < Ll; Tcol < Ll div 2 -> 758 | cind_list(L, Tcol, Ll, M, Ind, LD, W + Tlen + 1); 759 | true -> 760 | throw(no_good) 761 | end. 762 | 763 | cind_map([P | Ps], Col, Ll, M, Ind, LD, W) -> 764 | PW = cind_pair(P, Col, Ll, M, Ind, last_depth(Ps, LD), W), 765 | cind_pairs_tail(Ps, Col, Col + PW, Ll, M, Ind, LD, W + PW); 766 | cind_map(_, _Col, _Ll, _M, Ind, _LD, _W) -> 767 | Ind. 768 | 769 | cind_pairs_tail([{_, Len}=P | Ps], Col0, Col, Ll, M, Ind, LD, W) -> 770 | LD1 = last_depth(Ps, LD), 771 | ELen = 1 + Len, 772 | if 773 | LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_PAIR(P); 774 | LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_PAIR(P) -> 775 | cind_pairs_tail(Ps, Col0, Col + ELen, Ll, M, Ind, LD, W + ELen); 776 | true -> 777 | PW = cind_pair(P, Col0, Ll, M, Ind, LD1, 0), 778 | cind_pairs_tail(Ps, Col0, Col0 + PW, Ll, M, Ind, LD, PW) 779 | end; 780 | cind_pairs_tail(_, _Col0, _Col, _Ll, _M, Ind, _LD, _W) -> 781 | Ind. 782 | 783 | cind_pair({{map_pair, _Key, _Value}, Len}=Pair, Col, Ll, M, _Ind, LD, W) 784 | when Len < Ll - Col - LD, Len + W + LD =< M -> 785 | if 786 | ?ATM_PAIR(Pair) -> 787 | Len; 788 | true -> 789 | Ll 790 | end; 791 | cind_pair({{map_pair, K, V}, _Len}, Col0, Ll, M, Ind, LD, W0) -> 792 | cind(K, Col0, Ll, M, Ind, LD, W0), 793 | I = map_value_indent(Ind), 794 | cind(V, Col0 + I, Ll, M, Ind, LD, 0), 795 | Ll. 796 | 797 | map_value_indent(TInd) -> 798 | case TInd > 0 of 799 | true -> 800 | TInd; 801 | false -> 802 | 4 803 | end. 804 | 805 | cind_record([F | Fs], Nlen, Col0, Ll, M, Ind, LD, W0) -> 806 | Nind = Nlen + 1, 807 | {Col, W} = cind_rec(Nind, Col0, Ll, M, Ind, W0), 808 | FW = cind_field(F, Col, Ll, M, Ind, last_depth(Fs, LD), W), 809 | cind_fields_tail(Fs, Col, Col + FW, Ll, M, Ind, LD, W + FW); 810 | cind_record(_, _Nlen, _Col, _Ll, _M, Ind, _LD, _W) -> 811 | Ind. 812 | 813 | cind_fields_tail([{_, Len}=F | Fs], Col0, Col, Ll, M, Ind, LD, W) -> 814 | LD1 = last_depth(Fs, LD), 815 | ELen = 1 + Len, 816 | if 817 | LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM_FLD(F); 818 | LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM_FLD(F) -> 819 | cind_fields_tail(Fs, Col0, Col + ELen, Ll, M, Ind, LD, W + ELen); 820 | true -> 821 | FW = cind_field(F, Col0, Ll, M, Ind, LD1, 0), 822 | cind_fields_tail(Fs, Col0, Col + FW, Ll, M, Ind, LD, FW) 823 | end; 824 | cind_fields_tail(_, _Col0, _Col, _Ll, _M, Ind, _LD, _W) -> 825 | Ind. 826 | 827 | cind_field({{field, _N, _NL, _F}, Len}=Fl, Col, Ll, M, _Ind, LD, W) 828 | when Len < Ll - Col - LD, Len + W + LD =< M -> 829 | if 830 | ?ATM_FLD(Fl) -> 831 | Len; 832 | true -> 833 | Ll 834 | end; 835 | cind_field({{field, _Name, NameL, F}, _Len}, Col0, Ll, M, Ind, LD, W0) -> 836 | {Col, W} = cind_rec(NameL, Col0, Ll, M, Ind, W0 + NameL), 837 | cind(F, Col, Ll, M, Ind, LD, W), 838 | Ll. 839 | 840 | cind_rec(RInd, Col0, Ll, M, Ind, W0) -> 841 | Nl = (Ind > 0) and (RInd > Ind), 842 | DCol = case Nl of 843 | true -> Ind; 844 | false -> RInd 845 | end, 846 | Col = Col0 + DCol, 847 | if 848 | M + Col =< Ll; Col =< Ll div 2 -> 849 | W = case Nl of 850 | true -> 0; 851 | false -> W0 852 | end, 853 | {Col, W}; 854 | true -> 855 | throw(no_good) 856 | end. 857 | 858 | cind_list({dots, _}, _Col0, _Ll, _M, Ind, _LD, _W) -> 859 | Ind; 860 | cind_list([E | Es], Col0, Ll, M, Ind, LD, W) -> 861 | WE = cind_element(E, Col0, Ll, M, Ind, last_depth(Es, LD), W), 862 | cind_tail(Es, Col0, Col0 + WE, Ll, M, Ind, LD, W + WE). 863 | 864 | cind_tail([], _Col0, _Col, _Ll, _M, Ind, _LD, _W) -> 865 | Ind; 866 | cind_tail([{_, Len}=E | Es], Col0, Col, Ll, M, Ind, LD, W) -> 867 | LD1 = last_depth(Es, LD), 868 | ELen = 1 + Len, 869 | if 870 | LD1 =:= 0, ELen + 1 < Ll - Col, W + ELen + 1 =< M, ?ATM(E); 871 | LD1 > 0, ELen < Ll - Col - LD1, W + ELen + LD1 =< M, ?ATM(E) -> 872 | cind_tail(Es, Col0, Col + ELen, Ll, M, Ind, LD, W + ELen); 873 | true -> 874 | WE = cind_element(E, Col0, Ll, M, Ind, LD1, 0), 875 | cind_tail(Es, Col0, Col0 + WE, Ll, M, Ind, LD, WE) 876 | end; 877 | cind_tail({dots, _}, _Col0, _Col, _Ll, _M, Ind, _LD, _W) -> 878 | Ind; 879 | cind_tail({_, Len}=E, _Col0, Col, Ll, M, Ind, LD, W) 880 | when Len + 1 < Ll - Col - (LD + 1), 881 | Len + 1 + W + (LD + 1) =< M, 882 | ?ATM(E) -> 883 | Ind; 884 | cind_tail(E, _Col0, Col, Ll, M, Ind, LD, _W) -> 885 | cind(E, Col, Ll, M, Ind, LD + 1, 0). 886 | 887 | cind_element({_, Len}=E, Col, Ll, M, _Ind, LD, W) 888 | when Len < Ll - Col - LD, Len + W + LD =< M, ?ATM(E) -> 889 | Len; 890 | cind_element(E, Col, Ll, M, Ind, LD, W) -> 891 | cind(E, Col, Ll, M, Ind, LD, W), 892 | Ll. 893 | 894 | last_depth([_ | _], _LD) -> 895 | 0; 896 | last_depth(_, LD) -> 897 | LD + 1. 898 | 899 | while_fail([], _F, V) -> 900 | V; 901 | while_fail([A | As], F, V) -> 902 | try F(A) catch _ -> while_fail(As, F, V) end. 903 | 904 | %% make a string of N spaces 905 | indent(N) when is_integer(N), N > 0 -> 906 | chars($\s, N-1). 907 | 908 | %% prepend N spaces onto Ind 909 | indent(1, Ind) -> % Optimization of common case 910 | [$\s | Ind]; 911 | indent(4, Ind) -> % Optimization of common case 912 | S2 = [$\s, $\s], 913 | [S2, S2 | Ind]; 914 | indent(N, Ind) when is_integer(N), N > 0 -> 915 | [chars($\s, N) | Ind]. 916 | 917 | %% A deep version of string:chars/2 918 | chars(_C, 0) -> 919 | []; 920 | chars(C, 2) -> 921 | [C, C]; 922 | chars(C, 3) -> 923 | [C, C, C]; 924 | chars(C, N) when (N band 1) =:= 0 -> 925 | S = chars(C, N bsr 1), 926 | [S | S]; 927 | chars(C, N) -> 928 | S = chars(C, N bsr 1), 929 | [C, S | S]. 930 | 931 | get_option(Key, TupleList, Default) -> 932 | case lists:keyfind(Key, 1, TupleList) of 933 | false -> Default; 934 | {Key, Value} -> Value; 935 | _ -> Default 936 | end. 937 | -------------------------------------------------------------------------------- /src/3.4/lib.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1996-2017. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | -module(lib). 21 | 22 | -import(shell_profile,[beautify/2]). 23 | 24 | -export([flush_receive/0, error_message/2, progname/0, nonl/1, send/2, 25 | sendw/2, eval_str/1]). 26 | 27 | -export([extended_parse_exprs/1, extended_parse_term/1, 28 | subst_values_for_vars/2]). 29 | 30 | -export([format_exception/6, format_exception/7, 31 | format_stacktrace/4, format_stacktrace/5, 32 | format_call/4, format_call/5, format_fun/1, format_fun/2]). 33 | 34 | -spec flush_receive() -> 'ok'. 35 | 36 | flush_receive() -> 37 | receive 38 | _Any -> 39 | flush_receive() 40 | after 41 | 0 -> 42 | ok 43 | end. 44 | 45 | %% 46 | %% Functions for doing standard system format i/o. 47 | %% 48 | -spec error_message(Format, Args) -> 'ok' when 49 | Format :: io:format(), 50 | Args :: [term()]. 51 | 52 | error_message(Format, Args) -> 53 | io:format(<<"** ~ts **\n">>, [io_lib:format(Format, Args)]). 54 | 55 | %% Return the name of the script that starts (this) erlang 56 | %% 57 | -spec progname() -> atom(). 58 | 59 | progname() -> 60 | case init:get_argument(progname) of 61 | {ok, [[Prog]]} -> 62 | list_to_atom(Prog); 63 | _Other -> 64 | no_prog_name 65 | end. 66 | 67 | -spec nonl(String1) -> String2 when 68 | String1 :: string(), 69 | String2 :: string(). 70 | 71 | nonl([10]) -> []; 72 | nonl([]) -> []; 73 | nonl([H|T]) -> [H|nonl(T)]. 74 | 75 | -spec send(To, Msg) -> Msg when 76 | To :: pid() | atom() | {atom(), node()}, 77 | Msg :: term(). 78 | 79 | send(To, Msg) -> To ! Msg. 80 | 81 | -spec sendw(To, Msg) -> term() when 82 | To :: pid() | atom() | {atom(), node()}, 83 | Msg :: term(). 84 | 85 | sendw(To, Msg) -> 86 | To ! {self(), Msg}, 87 | receive 88 | Reply -> Reply 89 | end. 90 | 91 | %% eval_str(InStr) -> {ok, OutStr} | {error, ErrStr'} 92 | %% InStr must represent a body 93 | %% Note: If InStr is a binary it has to be a Latin-1 string. 94 | %% If you have a UTF-8 encoded binary you have to call 95 | %% unicode:characters_to_list/1 before the call to eval_str(). 96 | 97 | -define(result(F,D), lists:flatten(io_lib:format(F, D))). 98 | 99 | -spec eval_str(string() | unicode:latin1_binary()) -> 100 | {'ok', string()} | {'error', string()}. 101 | 102 | eval_str(Str) when is_list(Str) -> 103 | case erl_scan:tokens([], Str, 0) of 104 | {more, _} -> 105 | {error, "Incomplete form (missing .)??"}; 106 | {done, {ok, Toks, _}, Rest} -> 107 | case all_white(Rest) of 108 | true -> 109 | case erl_parse:parse_exprs(Toks) of 110 | {ok, Exprs} -> 111 | case catch erl_eval:exprs(Exprs, erl_eval:new_bindings()) of 112 | {value, Val, _} -> 113 | {ok, Val}; 114 | Other -> 115 | {error, ?result("*** eval: ~p", [Other])} 116 | end; 117 | {error, {_Line, Mod, Args}} -> 118 | Msg = ?result("*** ~ts",[Mod:format_error(Args)]), 119 | {error, Msg} 120 | end; 121 | false -> 122 | {error, ?result("Non-white space found after " 123 | "end-of-form :~ts", [Rest])} 124 | end 125 | end; 126 | eval_str(Bin) when is_binary(Bin) -> 127 | eval_str(binary_to_list(Bin)). 128 | 129 | all_white([$\s|T]) -> all_white(T); 130 | all_white([$\n|T]) -> all_white(T); 131 | all_white([$\t|T]) -> all_white(T); 132 | all_white([]) -> true; 133 | all_white(_) -> false. 134 | 135 | %% `Tokens' is assumed to have been scanned with the 'text' option. 136 | %% The annotations of the returned expressions are locations. 137 | %% 138 | %% Can handle pids, ports, references, and external funs ("items"). 139 | %% Known items are represented by variables in the erl_parse tree, and 140 | %% the items themselves are stored in the returned bindings. 141 | 142 | -spec extended_parse_exprs(Tokens) -> 143 | {'ok', ExprList, Bindings} | {'error', ErrorInfo} when 144 | Tokens :: [erl_scan:token()], 145 | ExprList :: [erl_parse:abstract_expr()], 146 | Bindings :: erl_eval:binding_struct(), 147 | ErrorInfo :: erl_parse:error_info(). 148 | 149 | extended_parse_exprs(Tokens) -> 150 | Ts = tokens_fixup(Tokens), 151 | case erl_parse:parse_exprs(Ts) of 152 | {ok, Exprs0} -> 153 | {Exprs, Bs} = expr_fixup(Exprs0), 154 | {ok, reset_expr_anno(Exprs), Bs}; 155 | _ErrorInfo -> 156 | erl_parse:parse_exprs(reset_token_anno(Ts)) 157 | end. 158 | 159 | tokens_fixup([]) -> []; 160 | tokens_fixup([T|Ts]=Ts0) -> 161 | try token_fixup(Ts0) of 162 | {NewT, NewTs} -> 163 | [NewT|tokens_fixup(NewTs)] 164 | catch 165 | _:_ -> 166 | [T|tokens_fixup(Ts)] 167 | end. 168 | 169 | token_fixup(Ts) -> 170 | {AnnoL, NewTs, FixupTag} = unscannable(Ts), 171 | String = lists:append([erl_anno:text(A) || A <- AnnoL]), 172 | _ = (fixup_fun(FixupTag))(String), 173 | NewAnno = erl_anno:set_text(fixup_text(FixupTag), hd(AnnoL)), 174 | {{string, NewAnno, String}, NewTs}. 175 | 176 | unscannable([{'#', A1}, {var, A2, 'Fun'}, {'<', A3}, {atom, A4, _}, 177 | {'.', A5}, {float, A6, _}, {'>', A7}|Ts]) -> 178 | {[A1, A2, A3, A4, A5, A6, A7], Ts, function}; 179 | unscannable([{'#', A1}, {var, A2, 'Fun'}, {'<', A3}, {atom, A4, _}, 180 | {'.', A5}, {atom, A6, _}, {'.', A7}, {integer, A8, _}, 181 | {'>', A9}|Ts]) -> 182 | {[A1, A2, A3, A4, A5, A6, A7, A8, A9], Ts, function}; 183 | unscannable([{'<', A1}, {float, A2, _}, {'.', A3}, {integer, A4, _}, 184 | {'>', A5}|Ts]) -> 185 | {[A1, A2, A3, A4, A5], Ts, pid}; 186 | unscannable([{'#', A1}, {var, A2, 'Port'}, {'<', A3}, {float, A4, _}, 187 | {'>', A5}|Ts]) -> 188 | {[A1, A2, A3, A4, A5], Ts, port}; 189 | unscannable([{'#', A1}, {var, A2, 'Ref'}, {'<', A3}, {float, A4, _}, 190 | {'.', A5}, {float, A6, _}, {'>', A7}|Ts]) -> 191 | {[A1, A2, A3, A4, A5, A6, A7], Ts, reference}. 192 | 193 | expr_fixup(Expr0) -> 194 | {Expr, Bs, _} = expr_fixup(Expr0, erl_eval:new_bindings(), 1), 195 | {Expr, Bs}. 196 | 197 | expr_fixup({string,A,S}=T, Bs0, I) -> 198 | try string_fixup(A, S) of 199 | Value -> 200 | Var = new_var(I), 201 | Bs = erl_eval:add_binding(Var, Value, Bs0), 202 | {{var, A, Var}, Bs, I+1} 203 | catch 204 | _:_ -> 205 | {T, Bs0, I} 206 | end; 207 | expr_fixup(Tuple, Bs0, I0) when is_tuple(Tuple) -> 208 | {L, Bs, I} = expr_fixup(tuple_to_list(Tuple), Bs0, I0), 209 | {list_to_tuple(L), Bs, I}; 210 | expr_fixup([E0|Es0], Bs0, I0) -> 211 | {E, Bs1, I1} = expr_fixup(E0, Bs0, I0), 212 | {Es, Bs, I} = expr_fixup(Es0, Bs1, I1), 213 | {[E|Es], Bs, I}; 214 | expr_fixup(T, Bs, I) -> 215 | {T, Bs, I}. 216 | 217 | string_fixup(A, S) -> 218 | Text = erl_anno:text(A), 219 | FixupTag = fixup_tag(Text, S), 220 | (fixup_fun(FixupTag))(S). 221 | 222 | new_var(I) -> 223 | list_to_atom(lists:concat(['__ExtendedParseExprs_', I, '__'])). 224 | 225 | reset_token_anno(Tokens) -> 226 | [setelement(2, T, (reset_anno())(element(2, T))) || T <- Tokens]. 227 | 228 | reset_expr_anno(Exprs) -> 229 | [erl_parse:map_anno(reset_anno(), E) || E <- Exprs]. 230 | 231 | reset_anno() -> 232 | fun(A) -> erl_anno:new(erl_anno:location(A)) end. 233 | 234 | fixup_fun(function) -> fun function/1; 235 | fixup_fun(pid) -> fun erlang:list_to_pid/1; 236 | fixup_fun(port) -> fun erlang:list_to_port/1; 237 | fixup_fun(reference) -> fun erlang:list_to_ref/1. 238 | 239 | function(S) -> 240 | %% External function. 241 | {ok, [_, _, _, 242 | {atom, _, Module}, _, 243 | {atom, _, Function}, _, 244 | {integer, _, Arity}|_], _} = erl_scan:string(S), 245 | erlang:make_fun(Module, Function, Arity). 246 | 247 | fixup_text(function) -> "function"; 248 | fixup_text(pid) -> "pid"; 249 | fixup_text(port) -> "port"; 250 | fixup_text(reference) -> "reference". 251 | 252 | fixup_tag("function", "#"++_) -> function; 253 | fixup_tag("pid", "<"++_) -> pid; 254 | fixup_tag("port", "#"++_) -> port; 255 | fixup_tag("reference", "#"++_) -> reference. 256 | 257 | %%% End of extended_parse_exprs. 258 | 259 | %% `Tokens' is assumed to have been scanned with the 'text' option. 260 | %% 261 | %% Can handle pids, ports, references, and external funs. 262 | 263 | -spec extended_parse_term(Tokens) -> 264 | {'ok', Term} | {'error', ErrorInfo} when 265 | Tokens :: [erl_scan:token()], 266 | Term :: term(), 267 | ErrorInfo :: erl_parse:error_info(). 268 | 269 | extended_parse_term(Tokens) -> 270 | case extended_parse_exprs(Tokens) of 271 | {ok, [Expr], Bindings} -> 272 | try normalise(Expr, Bindings) of 273 | Term -> 274 | {ok, Term} 275 | catch 276 | _:_ -> 277 | Loc = erl_anno:location(element(2, Expr)), 278 | {error,{Loc,?MODULE,"bad term"}} 279 | end; 280 | {ok, [_,Expr|_], _Bindings} -> 281 | Loc = erl_anno:location(element(2, Expr)), 282 | {error,{Loc,?MODULE,"bad term"}}; 283 | {error, _} = Error -> 284 | Error 285 | end. 286 | 287 | %% From erl_parse. 288 | normalise({var, _, V}, Bs) -> 289 | {value, Value} = erl_eval:binding(V, Bs), 290 | Value; 291 | normalise({char,_,C}, _Bs) -> C; 292 | normalise({integer,_,I}, _Bs) -> I; 293 | normalise({float,_,F}, _Bs) -> F; 294 | normalise({atom,_,A}, _Bs) -> A; 295 | normalise({string,_,S}, _Bs) -> S; 296 | normalise({nil,_}, _Bs) -> []; 297 | normalise({bin,_,Fs}, Bs) -> 298 | {value, B, _} = 299 | eval_bits:expr_grp(Fs, [], 300 | fun(E, _) -> 301 | {value, normalise(E, Bs), []} 302 | end, [], true), 303 | B; 304 | normalise({cons,_,Head,Tail}, Bs) -> 305 | [normalise(Head, Bs)|normalise(Tail, Bs)]; 306 | normalise({tuple,_,Args}, Bs) -> 307 | list_to_tuple(normalise_list(Args, Bs)); 308 | normalise({map,_,Pairs}, Bs) -> 309 | maps:from_list(lists:map(fun 310 | %% only allow '=>' 311 | ({map_field_assoc,_,K,V}) -> 312 | {normalise(K, Bs),normalise(V, Bs)} 313 | end, Pairs)); 314 | %% Special case for unary +/-. 315 | normalise({op,_,'+',{char,_,I}}, _Bs) -> I; 316 | normalise({op,_,'+',{integer,_,I}}, _Bs) -> I; 317 | normalise({op,_,'+',{float,_,F}}, _Bs) -> F; 318 | normalise({op,_,'-',{char,_,I}}, _Bs) -> -I; %Weird, but compatible! 319 | normalise({op,_,'-',{integer,_,I}}, _Bs) -> -I; 320 | normalise({op,_,'-',{float,_,F}}, _Bs) -> -F; 321 | normalise({'fun',_,{function,{atom,_,M},{atom,_,F},{integer,_,A}}}, _Bs) -> 322 | %% Since "#Fun" is recognized, "fun M:F/A" should be too. 323 | fun M:F/A. 324 | 325 | normalise_list([H|T], Bs) -> 326 | [normalise(H, Bs)|normalise_list(T, Bs)]; 327 | normalise_list([], _Bs) -> 328 | []. 329 | 330 | %% To be used on ExprList and Bindings returned from extended_parse_exprs(). 331 | %% Substitute {value, A, Item} for {var, A, ExtendedParseVar}. 332 | %% {value, A, Item} is a shell/erl_eval convention, and for example 333 | %% the linter cannot handle it. 334 | 335 | -spec subst_values_for_vars(ExprList, Bindings) -> [term()] when 336 | ExprList :: [erl_parse:abstract_expr()], 337 | Bindings :: erl_eval:binding_struct(). 338 | 339 | subst_values_for_vars({var, A, V}=Var, Bs) -> 340 | case erl_eval:binding(V, Bs) of 341 | {value, Value} -> 342 | {value, A, Value}; 343 | unbound -> 344 | Var 345 | end; 346 | subst_values_for_vars(L, Bs) when is_list(L) -> 347 | [subst_values_for_vars(E, Bs) || E <- L]; 348 | subst_values_for_vars(T, Bs) when is_tuple(T) -> 349 | list_to_tuple(subst_values_for_vars(tuple_to_list(T), Bs)); 350 | subst_values_for_vars(T, _Bs) -> 351 | T. 352 | 353 | %%% Formatting of exceptions, mfa:s and funs. 354 | 355 | %% -> iolist() (no \n at end) 356 | %% I is the current column, starting from 1 (it will be used 357 | %% as indentation whenever newline has been inserted); 358 | %% Class, Reason and StackTrace are the exception; 359 | %% FormatFun = fun(Term, I) -> iolist() formats terms; 360 | %% StackFun = fun(Mod, Fun, Arity) -> boolean() is used for trimming the 361 | %% end of the stack (typically calls to erl_eval are skipped). 362 | format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun) -> 363 | format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun, 364 | latin1). 365 | 366 | %% -> iolist() | unicode:charlist() (no \n at end) 367 | %% FormatFun = fun(Term, I) -> iolist() | unicode:charlist(). 368 | format_exception(I, Class, Reason, StackTrace, StackFun, FormatFun, Encoding) 369 | when is_integer(I), I >= 1, is_function(StackFun, 3), 370 | is_function(FormatFun, 2) -> 371 | S = n_spaces(I-1), 372 | {Term,Trace1,Trace} = analyze_exception(Class, Reason, StackTrace), 373 | Expl0 = explain_reason(Term, Class, Trace1, FormatFun, S, Encoding), 374 | FormatString = case Encoding of 375 | latin1 -> "\e[0;31m~s~s\e[0m"; 376 | _ -> "\e[0;31m~s~ts\e[0m" 377 | end, 378 | Expl = io_lib:fwrite(FormatString, [exited(Class), Expl0]), 379 | case format_stacktrace1(S, Trace, FormatFun, StackFun, Encoding) of 380 | [] -> Expl; 381 | Stack -> [Expl, $\n, Stack] 382 | end. 383 | 384 | %% -> iolist() (no \n at end) 385 | format_stacktrace(I, StackTrace, StackFun, FormatFun) -> 386 | format_stacktrace(I, StackTrace, StackFun, FormatFun, latin1). 387 | 388 | %% -> iolist() | unicode:charlist() (no \n at end) 389 | format_stacktrace(I, StackTrace, StackFun, FormatFun, Encoding) 390 | when is_integer(I), I >= 1, is_function(StackFun, 3), 391 | is_function(FormatFun, 2) -> 392 | S = n_spaces(I-1), 393 | format_stacktrace1(S, StackTrace, FormatFun, StackFun, Encoding). 394 | 395 | %% -> iolist() (no \n at end) 396 | format_call(I, ForMForFun, As, FormatFun) -> 397 | format_call(I, ForMForFun, As, FormatFun, latin1). 398 | 399 | %% -> iolist() | unicode:charlist() (no \n at end) 400 | format_call(I, ForMForFun, As, FormatFun, Enc) 401 | when is_integer(I), I >= 1, is_list(As), is_function(FormatFun, 2) -> 402 | format_call("", n_spaces(I-1), ForMForFun, As, FormatFun, Enc). 403 | 404 | %% -> iolist() (no \n at end) 405 | format_fun(Fun) -> 406 | format_fun(Fun, latin1). 407 | 408 | %% -> iolist() (no \n at end) 409 | format_fun(Fun, Enc) when is_function(Fun) -> 410 | {module, M} = erlang:fun_info(Fun, module), 411 | {name, F} = erlang:fun_info(Fun, name), 412 | {arity, A} = erlang:fun_info(Fun, arity), 413 | case erlang:fun_info(Fun, type) of 414 | {type, local} when F =:= "" -> 415 | io_lib:fwrite(<<"~w">>, [Fun]); 416 | {type, local} when M =:= erl_eval -> 417 | io_lib:fwrite(<<"interpreted function with arity ~w">>, [A]); 418 | {type, local} -> 419 | mfa_to_string(M, F, A, Enc); 420 | {type, external} -> 421 | mfa_to_string(M, F, A, Enc) 422 | end. 423 | 424 | analyze_exception(error, Term, Stack) -> 425 | case {is_stacktrace(Stack), Stack, Term} of 426 | {true, [{_,_,As,_}=MFAL|MFAs], function_clause} when is_list(As) -> 427 | {Term,[MFAL],MFAs}; 428 | {true, [{shell,F,A,_}], function_clause} when is_integer(A) -> 429 | {Term, [{F,A}], []}; 430 | {true, [{_,_,_,_}=MFAL|MFAs], undef} -> 431 | {Term,[MFAL],MFAs}; 432 | {true, _, _} -> 433 | {Term,[],Stack}; 434 | {false, _, _} -> 435 | {{Term,Stack},[],[]} 436 | end; 437 | analyze_exception(_Class, Term, Stack) -> 438 | case is_stacktrace(Stack) of 439 | true -> 440 | {Term,[],Stack}; 441 | false -> 442 | {{Term,Stack},[],[]} 443 | end. 444 | 445 | is_stacktrace([]) -> 446 | true; 447 | is_stacktrace([{M,F,A,I}|Fs]) 448 | when is_atom(M), is_atom(F), is_integer(A), is_list(I) -> 449 | is_stacktrace(Fs); 450 | is_stacktrace([{M,F,As,I}|Fs]) 451 | when is_atom(M), is_atom(F), length(As) >= 0, is_list(I) -> 452 | is_stacktrace(Fs); 453 | is_stacktrace(_) -> 454 | false. 455 | 456 | %% ERTS exit codes (some of them are also returned by erl_eval): 457 | explain_reason(badarg, error, [], _PF, _S, _Enc) -> 458 | <<"bad argument">>; 459 | explain_reason({badarg,V}, error=Cl, [], PF, S, _Enc) -> % orelse, andalso 460 | format_value(V, <<"bad argument: ">>, Cl, PF, S); 461 | explain_reason(badarith, error, [], _PF, _S, _Enc) -> 462 | <<"an error occurred when evaluating an arithmetic expression">>; 463 | explain_reason({badarity,{Fun,As}}, error, [], _PF, _S, Enc) 464 | when is_function(Fun) -> 465 | %% Only the arity is displayed, not the arguments As. 466 | io_lib:fwrite(<<"~ts called with ~s">>, 467 | [format_fun(Fun, Enc), argss(length(As))]); 468 | explain_reason({badfun,Term}, error=Cl, [], PF, S, _Enc) -> 469 | format_value(Term, <<"bad function ">>, Cl, PF, S); 470 | explain_reason({badmatch,Term}, error=Cl, [], PF, S, _Enc) -> 471 | Str = <<"no match of right hand side value ">>, 472 | format_value(Term, Str, Cl, PF, S); 473 | explain_reason({case_clause,V}, error=Cl, [], PF, S, _Enc) -> 474 | %% "there is no case clause with a true guard sequence and a 475 | %% pattern matching..." 476 | format_value(V, <<"no case clause matching ">>, Cl, PF, S); 477 | explain_reason(function_clause, error, [{F,A}], _PF, _S, _Enc) -> 478 | %% Shell commands 479 | FAs = io_lib:fwrite(<<"~w/~w">>, [F, A]), 480 | [<<"no function clause matching call to ">> | FAs]; 481 | explain_reason(function_clause, error=Cl, [{M,F,As,Loc}], PF, S, Enc) -> 482 | Str = <<"no function clause matching ">>, 483 | [format_errstr_call(Str, Cl, {M,F}, As, PF, S, Enc),$\s|location(Loc)]; 484 | explain_reason(if_clause, error, [], _PF, _S, _Enc) -> 485 | <<"no true branch found when evaluating an if expression">>; 486 | explain_reason(noproc, error, [], _PF, _S, _Enc) -> 487 | <<"no such process or port">>; 488 | explain_reason(notalive, error, [], _PF, _S, _Enc) -> 489 | <<"the node cannot be part of a distributed system">>; 490 | explain_reason(system_limit, error, [], _PF, _S, _Enc) -> 491 | <<"a system limit has been reached">>; 492 | explain_reason(timeout_value, error, [], _PF, _S, _Enc) -> 493 | <<"bad receive timeout value">>; 494 | explain_reason({try_clause,V}, error=Cl, [], PF, S, _Enc) -> 495 | %% "there is no try clause with a true guard sequence and a 496 | %% pattern matching..." 497 | format_value(V, <<"no try clause matching ">>, Cl, PF, S); 498 | explain_reason(undef, error, [{M,F,A,_}], _PF, _S, Enc) -> 499 | %% Only the arity is displayed, not the arguments, if there are any. 500 | io_lib:fwrite(<<"undefined function ~ts">>, 501 | [mfa_to_string(M, F, n_args(A), Enc)]); 502 | explain_reason({shell_undef,F,A,_}, error, [], _PF, _S, Enc) -> 503 | %% Give nicer reports for undefined shell functions 504 | %% (but not when the user actively calls shell_default:F(...)). 505 | FS = to_string(F, Enc), 506 | io_lib:fwrite(<<"undefined shell command ~ts/~w">>, [FS, n_args(A)]); 507 | %% Exit codes returned by erl_eval only: 508 | explain_reason({argument_limit,_Fun}, error, [], _PF, _S, _Enc) -> 509 | io_lib:fwrite(<<"limit of number of arguments to interpreted function" 510 | " exceeded">>, []); 511 | explain_reason({bad_filter,V}, error=Cl, [], PF, S, _Enc) -> 512 | format_value(V, <<"bad filter ">>, Cl, PF, S); 513 | explain_reason({bad_generator,V}, error=Cl, [], PF, S, _Enc) -> 514 | format_value(V, <<"bad generator ">>, Cl, PF, S); 515 | explain_reason({unbound,V}, error, [], _PF, _S, _Enc) -> 516 | io_lib:fwrite(<<"variable ~w is unbound">>, [V]); 517 | %% Exit codes local to the shell module (restricted shell): 518 | explain_reason({restricted_shell_bad_return, V}, exit=Cl, [], PF, S, _Enc) -> 519 | Str = <<"restricted shell module returned bad value ">>, 520 | format_value(V, Str, Cl, PF, S); 521 | explain_reason({restricted_shell_disallowed,{ForMF,As}}, 522 | exit=Cl, [], PF, S, Enc) -> 523 | %% ForMF can be a fun, but not a shell fun. 524 | Str = <<"restricted shell does not allow ">>, 525 | format_errstr_call(Str, Cl, ForMF, As, PF, S, Enc); 526 | explain_reason(restricted_shell_started, exit, [], _PF, _S, _Enc) -> 527 | <<"restricted shell starts now">>; 528 | explain_reason(restricted_shell_stopped, exit, [], _PF, _S, _Enc) -> 529 | <<"restricted shell stopped">>; 530 | %% Other exit code: 531 | explain_reason(Reason, Class, [], PF, S, _Enc) -> 532 | PF(Reason, (iolist_size(S)+1) + exited_size(Class)). 533 | 534 | n_args(A) when is_integer(A) -> 535 | A; 536 | n_args(As) when is_list(As) -> 537 | length(As). 538 | 539 | argss(0) -> 540 | <<"no arguments">>; 541 | argss(1) -> 542 | <<"one argument">>; 543 | argss(2) -> 544 | <<"two arguments">>; 545 | argss(I) -> 546 | io_lib:fwrite(<<"~w arguments">>, [I]). 547 | 548 | format_stacktrace1(S0, Stack0, PF, SF, Enc) -> 549 | Stack1 = lists:dropwhile(fun({M,F,A,_}) -> SF(M, F, A) 550 | end, lists:reverse(Stack0)), 551 | S = [" " | S0], 552 | Stack = lists:reverse(Stack1), 553 | format_stacktrace2(S, Stack, 1, PF, Enc). 554 | 555 | format_stacktrace2(S, [{M,F,A,L}|Fs], N, PF, Enc) when is_integer(A) -> 556 | [io_lib:fwrite(<<"\e[0;33m ~s~s ~ts ~s \e[0m">>, 557 | [sep(N, S), origin(N, M, F, A), 558 | mfa_to_string(M, F, A, Enc), 559 | location(L)]) 560 | | format_stacktrace2(S, Fs, N + 1, PF, Enc)]; 561 | format_stacktrace2(S, [{M,F,As,_}|Fs], N, PF, Enc) when is_list(As) -> 562 | A = length(As), 563 | CalledAs = [S,<<" called as ">>], 564 | C = format_call("", CalledAs, {M,F}, As, PF, Enc), 565 | [io_lib:fwrite(<<"\e[0;33m ~s~s ~ts\n~s~ts \e[0m">>, 566 | [sep(N, S), origin(N, M, F, A), 567 | mfa_to_string(M, F, A, Enc), 568 | CalledAs, C]) 569 | | format_stacktrace2(S, Fs, N + 1, PF, Enc)]; 570 | format_stacktrace2(_S, [], _N, _PF, _Enc) -> 571 | "". 572 | 573 | location(L) -> 574 | File = proplists:get_value(file, L), 575 | Line = proplists:get_value(line, L), 576 | if 577 | File =/= undefined, Line =/= undefined -> 578 | io_lib:format("(~s, line ~w)", [File, Line]); 579 | true -> 580 | "" 581 | end. 582 | 583 | sep(1, S) -> S; 584 | sep(_, S) -> [$\n | S]. 585 | 586 | origin(1, M, F, A) -> 587 | case is_op({M, F}, n_args(A)) of 588 | {yes, F} -> <<"in operator ">>; 589 | no -> <<"in function ">> 590 | end; 591 | origin(_N, _M, _F, _A) -> 592 | <<"in call from">>. 593 | 594 | format_errstr_call(ErrStr, Class, ForMForFun, As, PF, Pre0, Enc) -> 595 | Pre1 = [Pre0 | n_spaces(exited_size(Class))], 596 | format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc). 597 | 598 | format_call(ErrStr, Pre1, ForMForFun, As, PF, Enc) -> 599 | Arity = length(As), 600 | [ErrStr | 601 | case is_op(ForMForFun, Arity) of 602 | {yes,Op} -> 603 | format_op(ErrStr, Pre1, Op, As, PF, Enc); 604 | no -> 605 | MFs = mf_to_string(ForMForFun, Arity, Enc), 606 | I1 = string:length([Pre1,ErrStr|MFs]), 607 | S1 = pp_arguments(PF, As, I1, Enc), 608 | S2 = pp_arguments(PF, As, string:length([Pre1|MFs]), Enc), 609 | Long = count_nl(pp_arguments(PF, [a2345,b2345], I1, Enc)) > 0, 610 | case Long or (count_nl(S2) < count_nl(S1)) of 611 | true -> 612 | [$\n, Pre1, MFs, S2]; 613 | false -> 614 | [MFs, S1] 615 | end 616 | end]. 617 | 618 | format_op(ErrStr, Pre, Op, [A1], PF, _Enc) -> 619 | OpS = io_lib:fwrite(<<"~s ">>, [Op]), 620 | I1 = iolist_size([ErrStr,Pre,OpS]), 621 | [OpS | PF(A1, I1+1)]; 622 | format_op(ErrStr, Pre, Op, [A1, A2], PF, Enc) -> 623 | I1 = iolist_size([ErrStr,Pre]), 624 | S1 = PF(A1, I1+1), 625 | S2 = PF(A2, I1+1), 626 | OpS = atom_to_list(Op), 627 | Pre1 = [$\n | n_spaces(I1)], 628 | case count_nl(S1) > 0 of 629 | true -> 630 | [S1,Pre1,OpS,Pre1|S2]; 631 | false -> 632 | OpS2 = io_lib:fwrite(<<" ~s ">>, [Op]), 633 | Size1 = iolist_size([ErrStr,Pre|OpS2]), 634 | {Size2,S1_2} = size(Enc, S1), 635 | S2_2 = PF(A2, Size1+Size2+1), 636 | case count_nl(S2) < count_nl(S2_2) of 637 | true -> 638 | [S1_2,Pre1,OpS,Pre1|S2]; 639 | false -> 640 | [S1_2,OpS2|S2_2] 641 | end 642 | end. 643 | 644 | pp_arguments(PF, As, I, Enc) -> 645 | case {As, printable_list(Enc, As)} of 646 | {[Int | T], true} -> 647 | L = integer_to_list(Int), 648 | Ll = length(L), 649 | A = list_to_atom(lists:duplicate(Ll, $a)), 650 | S0 = unicode:characters_to_list(PF([A | T], I+1), Enc), 651 | brackets_to_parens([$[,L,string:sub_string(S0, 2+Ll)], Enc); 652 | _ -> 653 | brackets_to_parens(PF(As, I+1), Enc) 654 | end. 655 | 656 | brackets_to_parens(S, Enc) -> 657 | B = unicode:characters_to_binary(S, Enc), 658 | Sz = byte_size(B) - 24, 659 | <<"\e[0;33m[\e[0m",R:Sz/binary,"\e[0;33m]\e[0m">> = B, 660 | [beautify(rb,"("),R,beautify(rb,")")]. 661 | 662 | printable_list(latin1, As) -> 663 | io_lib:printable_latin1_list(As); 664 | printable_list(_, As) -> 665 | io_lib:printable_list(As). 666 | 667 | mfa_to_string(M, F, A, Enc) -> 668 | io_lib:fwrite(<<"~ts/~w">>, [mf_to_string({M, F}, A, Enc), A]). 669 | 670 | mf_to_string({M, F}, A, Enc) -> 671 | case erl_internal:bif(M, F, A) of 672 | true -> 673 | io_lib:fwrite(<<"~w">>, [F]); 674 | false -> 675 | case is_op({M, F}, A) of 676 | {yes, '/'} -> 677 | io_lib:fwrite(<<"~w">>, [F]); 678 | {yes, F} -> 679 | atom_to_list(F); 680 | no -> 681 | FS = to_string(F, Enc), 682 | io_lib:fwrite(<<"~w:~ts">>, [M, FS]) 683 | end 684 | end; 685 | mf_to_string(Fun, _A, Enc) when is_function(Fun) -> 686 | format_fun(Fun, Enc); 687 | mf_to_string(F, _A, Enc) -> 688 | FS = to_string(F, Enc), 689 | io_lib:fwrite(<<"~ts">>, [FS]). 690 | 691 | format_value(V, ErrStr, Class, PF, S) -> 692 | Pre1Sz = exited_size(Class), 693 | S1 = PF(V, Pre1Sz + iolist_size([S, ErrStr])+1), 694 | [ErrStr | case count_nl(S1) of 695 | N1 when N1 > 1 -> 696 | S2 = PF(V, iolist_size(S) + 1 + Pre1Sz), 697 | case count_nl(S2) < N1 of 698 | true -> 699 | [$\n, S, n_spaces(Pre1Sz) | S2]; 700 | false -> 701 | S1 702 | end; 703 | _ -> 704 | S1 705 | end]. 706 | 707 | %% Handles deep lists, but not all iolists. 708 | count_nl([E | Es]) -> 709 | count_nl(E) + count_nl(Es); 710 | count_nl($\n) -> 711 | 1; 712 | count_nl(Bin) when is_binary(Bin) -> 713 | count_nl(binary_to_list(Bin)); 714 | count_nl(_) -> 715 | 0. 716 | 717 | n_spaces(N) -> 718 | lists:duplicate(N, $\s). 719 | 720 | is_op(ForMForFun, A) -> 721 | try 722 | {erlang,F} = ForMForFun, 723 | _ = erl_internal:op_type(F, A), 724 | {yes,F} 725 | catch error:_ -> no 726 | end. 727 | 728 | exited_size(Class) -> 729 | iolist_size(exited(Class)). 730 | 731 | exited(error) -> 732 | <<"exception error: ">>; 733 | exited(exit) -> 734 | <<"exception exit: ">>; 735 | exited(throw) -> 736 | <<"exception throw: ">>. 737 | 738 | to_string(A, latin1) -> 739 | io_lib:write_atom_as_latin1(A); 740 | to_string(A, _) -> 741 | io_lib:write_atom(A). 742 | 743 | size(latin1, S) -> 744 | {iolist_size(S),S}; 745 | size(_, S0) -> 746 | S = unicode:characters_to_list(S0, unicode), 747 | true = is_list(S), 748 | {string:length(S),S}. 749 | -------------------------------------------------------------------------------- /src/3.4/shell.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% %CopyrightBegin% 3 | %% 4 | %% Copyright Ericsson AB 1996-2017. All Rights Reserved. 5 | %% 6 | %% Licensed under the Apache License, Version 2.0 (the "License"); 7 | %% you may not use this file except in compliance with the License. 8 | %% You may obtain a copy of the License at 9 | %% 10 | %% http://www.apache.org/licenses/LICENSE-2.0 11 | %% 12 | %% Unless required by applicable law or agreed to in writing, software 13 | %% distributed under the License is distributed on an "AS IS" BASIS, 14 | %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | %% See the License for the specific language governing permissions and 16 | %% limitations under the License. 17 | %% 18 | %% %CopyrightEnd% 19 | %% 20 | -module(shell). 21 | 22 | -export([start/0, start/1, start/2, server/1, server/2, history/1, results/1]). 23 | -export([whereis_evaluator/0, whereis_evaluator/1]). 24 | -export([start_restricted/1, stop_restricted/0]). 25 | -export([local_allowed/3, non_local_allowed/3]). 26 | -export([catch_exception/1, prompt_func/1, strings/1]). 27 | 28 | -import(shell_profile,[beautify/2]). 29 | 30 | -define(LINEMAX, 30). 31 | -define(CHAR_MAX, 60). 32 | -define(DEF_HISTORY, 20). 33 | -define(DEF_RESULTS, 20). 34 | -define(DEF_CATCH_EXCEPTION, false). 35 | -define(DEF_PROMPT_FUNC, default). 36 | -define(DEF_STRINGS, true). 37 | 38 | -define(RECORDS, shell_records). 39 | 40 | -define(MAXSIZE_HEAPBINARY, 64). 41 | 42 | %% When used as the fallback restricted shell callback module... 43 | local_allowed(q,[],State) -> 44 | {true,State}; 45 | local_allowed(_,_,State) -> 46 | {false,State}. 47 | 48 | non_local_allowed({init,stop},[],State) -> 49 | {true,State}; 50 | non_local_allowed(_,_,State) -> 51 | {false,State}. 52 | 53 | -spec start() -> pid(). 54 | 55 | start() -> 56 | start(false, false). 57 | 58 | start(init) -> 59 | start(false, true); 60 | start(NoCtrlG) -> 61 | start(NoCtrlG, false). 62 | 63 | start(NoCtrlG, StartSync) -> 64 | _ = code:ensure_loaded(user_default), 65 | spawn(fun() -> server(NoCtrlG, StartSync) end). 66 | 67 | %% Find the pid of the current evaluator process. 68 | -spec whereis_evaluator() -> 'undefined' | pid(). 69 | 70 | whereis_evaluator() -> 71 | %% locate top group leader, always registered as user 72 | %% can be implemented by group (normally) or user 73 | %% (if oldshell or noshell) 74 | case whereis(user) of 75 | undefined -> 76 | undefined; 77 | User -> 78 | %% get user_drv pid from group, or shell pid from user 79 | case group:interfaces(User) of 80 | [] -> % old- or noshell 81 | case user:interfaces(User) of 82 | [] -> 83 | undefined; 84 | [{shell,Shell}] -> 85 | whereis_evaluator(Shell) 86 | end; 87 | [{user_drv,UserDrv}] -> 88 | %% get current group pid from user_drv 89 | case user_drv:interfaces(UserDrv) of 90 | [] -> 91 | undefined; 92 | [{current_group,Group}] -> 93 | %% get shell pid from group 94 | GrIfs = group:interfaces(Group), 95 | case lists:keyfind(shell, 1, GrIfs) of 96 | {shell, Shell} -> 97 | whereis_evaluator(Shell); 98 | false -> 99 | undefined 100 | end 101 | end 102 | end 103 | end. 104 | 105 | -spec whereis_evaluator(pid()) -> 'undefined' | pid(). 106 | 107 | whereis_evaluator(Shell) -> 108 | case process_info(Shell, dictionary) of 109 | {dictionary,Dict} -> 110 | case lists:keyfind(evaluator, 1, Dict) of 111 | {_, Eval} when is_pid(Eval) -> 112 | Eval; 113 | _ -> 114 | undefined 115 | end; 116 | _ -> 117 | undefined 118 | end. 119 | 120 | %% Call this function to start a user restricted shell 121 | %% from a normal shell session. 122 | -spec start_restricted(Module) -> {'error', Reason} when 123 | Module :: module(), 124 | Reason :: code:load_error_rsn(). 125 | 126 | start_restricted(RShMod) when is_atom(RShMod) -> 127 | case code:ensure_loaded(RShMod) of 128 | {module,RShMod} -> 129 | application:set_env(stdlib, restricted_shell, RShMod), 130 | exit(restricted_shell_started); 131 | {error,What} = Error -> 132 | error_logger:error_report( 133 | lists:flatten( 134 | io_lib:fwrite( 135 | "Restricted shell module ~w not found: ~tp\n", 136 | [RShMod,What]))), 137 | Error 138 | end. 139 | 140 | -spec stop_restricted() -> no_return(). 141 | 142 | stop_restricted() -> 143 | application:unset_env(stdlib, restricted_shell), 144 | exit(restricted_shell_stopped). 145 | 146 | -spec server(boolean(), boolean()) -> 'terminated'. 147 | 148 | server(NoCtrlG, StartSync) -> 149 | put(no_control_g, NoCtrlG), 150 | server(StartSync). 151 | 152 | 153 | %%% The shell should not start until the system is up and running. 154 | %%% We subscribe with init to get a notification of when. 155 | 156 | %%% In older releases we didn't syncronize the shell with init, but let it 157 | %%% start in parallell with other system processes. This was bad since 158 | %%% accessing the shell too early could interfere with the boot procedure. 159 | %%% Still, by means of a flag, we make it possible to start the shell the 160 | %%% old way (for backwards compatibility reasons). This should however not 161 | %%% be used unless for very special reasons necessary. 162 | 163 | -spec server(boolean()) -> 'terminated'. 164 | 165 | server(StartSync) -> 166 | case init:get_argument(async_shell_start) of 167 | {ok,_} -> 168 | ok; % no sync with init 169 | _ when not StartSync -> 170 | ok; 171 | _ -> 172 | case init:notify_when_started(self()) of 173 | started -> 174 | ok; 175 | _ -> 176 | init:wait_until_started() 177 | end 178 | end, 179 | %% Our spawner has fixed the process groups. 180 | Bs = erl_eval:new_bindings(), 181 | 182 | %% Use an Ets table for record definitions. It takes too long to 183 | %% send a huge term to and from the evaluator. Ets makes it 184 | %% possible to have thousands of record definitions. 185 | RT = ets:new(?RECORDS, [public,ordered_set]), 186 | _ = initiate_records(Bs, RT), 187 | process_flag(trap_exit, true), 188 | 189 | %% Check if we're in user restricted mode. 190 | RShErr = 191 | case application:get_env(stdlib, restricted_shell) of 192 | {ok,RShMod} when is_atom(RShMod) -> 193 | io:fwrite(<<"Restricted ">>, []), 194 | case code:ensure_loaded(RShMod) of 195 | {module,RShMod} -> 196 | undefined; 197 | {error,What} -> 198 | {RShMod,What} 199 | end; 200 | {ok, Term} -> 201 | {Term,not_an_atom}; 202 | undefined -> 203 | undefined 204 | end, 205 | 206 | case get(no_control_g) of 207 | true -> 208 | io:fwrite(<<"Eshell V~s\n">>, [erlang:system_info(version)]); 209 | _undefined_or_false -> 210 | io:fwrite(<<"Eshell V~s (abort with ^G)\n">>, 211 | [erlang:system_info(version)]) 212 | end, 213 | erase(no_control_g), 214 | 215 | case RShErr of 216 | undefined -> 217 | ok; 218 | {RShMod2,What2} -> 219 | io:fwrite( 220 | ("Warning! Restricted shell module ~w not found: ~tp.\n" 221 | "Only the commands q() and init:stop() will be allowed!\n"), 222 | [RShMod2,What2]), 223 | application:set_env(stdlib, restricted_shell, ?MODULE) 224 | end, 225 | 226 | {History,Results} = check_and_get_history_and_results(), 227 | server_loop(0, start_eval(Bs, RT, []), Bs, RT, [], History, Results). 228 | 229 | server_loop(N0, Eval_0, Bs00, RT, Ds00, History0, Results0) -> 230 | N = N0 + 1, 231 | {Eval_1,Bs0,Ds0,Prompt} = prompt(N, Eval_0, Bs00, RT, Ds00), 232 | {Res,Eval0} = get_command(Prompt, Eval_1, Bs0, RT, Ds0), 233 | case Res of 234 | {ok,Es0,XBs} -> 235 | Es1 = lib:subst_values_for_vars(Es0, XBs), 236 | case expand_hist(Es1, N) of 237 | {ok,Es} -> 238 | {V,Eval,Bs,Ds} = shell_cmd(Es, Eval0, Bs0, RT, Ds0, cmd), 239 | {History,Results} = check_and_get_history_and_results(), 240 | add_cmd(N, Es, V), 241 | HB1 = del_cmd(command, N - History, N - History0, false), 242 | HB = del_cmd(result, N - Results, N - Results0, HB1), 243 | %% The following test makes sure that large binaries 244 | %% (outside of the heap) are garbage collected as soon 245 | %% as possible. 246 | if 247 | HB -> 248 | garb(self()); 249 | true -> 250 | ok 251 | end, 252 | server_loop(N, Eval, Bs, RT, Ds, History, Results); 253 | {error,E} -> 254 | fwrite_severity(benign, <<"~ts">>, [E]), 255 | server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0) 256 | end; 257 | {error,{Line,Mod,What}} -> 258 | fwrite_severity(benign, <<"~w: ~ts">>, 259 | [Line, Mod:format_error(What)]), 260 | server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0); 261 | {error,terminated} -> %Io process terminated 262 | exit(Eval0, kill), 263 | terminated; 264 | {error,interrupted} -> %Io process interrupted us 265 | exit(Eval0, kill), 266 | {_,Eval,_,_} = shell_rep(Eval0, Bs0, RT, Ds0), 267 | server_loop(N0, Eval, Bs0, RT, Ds0, History0, Results0); 268 | {error,tokens} -> %Most probably character > 255 269 | fwrite_severity(benign, <<"~w: Invalid tokens.">>, 270 | [N]), 271 | server_loop(N0, Eval0, Bs0, RT, Ds0, History0, Results0); 272 | eof -> 273 | fwrite_severity(fatal, <<"Terminating erlang (~w)">>, [node()]), 274 | halt() 275 | end. 276 | 277 | get_command(Prompt, Eval, Bs, RT, Ds) -> 278 | Parse = 279 | fun() -> 280 | exit( 281 | case 282 | io:scan_erl_exprs(group_leader(), Prompt, 1, [text]) 283 | of 284 | {ok,Toks,_EndPos} -> 285 | lib:extended_parse_exprs(Toks); 286 | {eof,_EndPos} -> 287 | eof; 288 | {error,ErrorInfo,_EndPos} -> 289 | %% Skip the rest of the line: 290 | Opts = io:getopts(), 291 | TmpOpts = lists:keyreplace(echo, 1, Opts, 292 | {echo, false}), 293 | _ = io:setopts(TmpOpts), 294 | _ = io:get_line(''), 295 | _ = io:setopts(Opts), 296 | {error,ErrorInfo}; 297 | Else -> 298 | Else 299 | end 300 | ) 301 | end, 302 | Pid = spawn_link(Parse), 303 | get_command1(Pid, Eval, Bs, RT, Ds). 304 | 305 | get_command1(Pid, Eval, Bs, RT, Ds) -> 306 | receive 307 | {'EXIT', Pid, Res} -> 308 | {Res, Eval}; 309 | {'EXIT', Eval, {Reason,Stacktrace}} -> 310 | report_exception(error, {Reason,Stacktrace}, RT), 311 | get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds); 312 | {'EXIT', Eval, Reason} -> 313 | report_exception(error, {Reason,[]}, RT), 314 | get_command1(Pid, start_eval(Bs, RT, Ds), Bs, RT, Ds) 315 | end. 316 | 317 | prompt(N, Eval0, Bs0, RT, Ds0) -> 318 | case get_prompt_func() of 319 | {M,F} -> 320 | L = [{history,N}], 321 | A = erl_anno:new(1), 322 | C = {call,A,{remote,A,{atom,A,M},{atom,A,F}},[{value,A,L}]}, 323 | {V,Eval,Bs,Ds} = shell_cmd([C], Eval0, Bs0, RT, Ds0, pmt), 324 | {Eval,Bs,Ds,case V of 325 | {pmt,Val} -> 326 | Val; 327 | _ -> 328 | bad_prompt_func({M,F}), 329 | default_prompt(N) 330 | end}; 331 | default -> 332 | {Eval0,Bs0,Ds0,default_prompt(N)} 333 | end. 334 | 335 | get_prompt_func() -> 336 | case application:get_env(stdlib, shell_prompt_func) of 337 | {ok,{M,F}=PromptFunc} when is_atom(M), is_atom(F) -> 338 | PromptFunc; 339 | {ok,default=Default} -> 340 | Default; 341 | {ok,Term} -> 342 | bad_prompt_func(Term), 343 | default; 344 | undefined -> 345 | default 346 | end. 347 | 348 | bad_prompt_func(M) -> 349 | fwrite_severity(benign, "Bad prompt function: ~tp", [M]). 350 | 351 | default_prompt(N) -> 352 | %% Don't bother flattening the list irrespective of what the 353 | %% I/O-protocol states. 354 | case is_alive() of 355 | true -> io_lib:format(<<"(~s)~w> ">>, [node(), N]); 356 | false -> 357 | Prompt = io_lib:format(<<"~w> ">>, [N]), 358 | beautify(prompt,Prompt) 359 | end. 360 | 361 | %% expand_hist(Expressions, CommandNumber) 362 | %% Preprocess the expression list replacing all history list commands 363 | %% with their expansions. 364 | 365 | expand_hist(Es, C) -> 366 | catch {ok,expand_exprs(Es, C)}. 367 | 368 | expand_exprs([E|Es], C) -> 369 | [expand_expr(E, C)|expand_exprs(Es, C)]; 370 | expand_exprs([], _C) -> 371 | []. 372 | 373 | expand_expr({cons,L,H,T}, C) -> 374 | {cons,L,expand_expr(H, C),expand_expr(T, C)}; 375 | expand_expr({lc,L,E,Qs}, C) -> 376 | {lc,L,expand_expr(E, C),expand_quals(Qs, C)}; 377 | expand_expr({bc,L,E,Qs}, C) -> 378 | {bc,L,expand_expr(E, C),expand_quals(Qs, C)}; 379 | expand_expr({tuple,L,Elts}, C) -> 380 | {tuple,L,expand_exprs(Elts, C)}; 381 | expand_expr({map,L,Es}, C) -> 382 | {map,L,expand_exprs(Es, C)}; 383 | expand_expr({map,L,Arg,Es}, C) -> 384 | {map,L,expand_expr(Arg, C),expand_exprs(Es, C)}; 385 | expand_expr({map_field_assoc,L,K,V}, C) -> 386 | {map_field_assoc,L,expand_expr(K, C),expand_expr(V, C)}; 387 | expand_expr({map_field_exact,L,K,V}, C) -> 388 | {map_field_exact,L,expand_expr(K, C),expand_expr(V, C)}; 389 | expand_expr({record_index,L,Name,F}, C) -> 390 | {record_index,L,Name,expand_expr(F, C)}; 391 | expand_expr({record,L,Name,Is}, C) -> 392 | {record,L,Name,expand_fields(Is, C)}; 393 | expand_expr({record_field,L,R,Name,F}, C) -> 394 | {record_field,L,expand_expr(R, C),Name,expand_expr(F, C)}; 395 | expand_expr({record,L,R,Name,Ups}, C) -> 396 | {record,L,expand_expr(R, C),Name,expand_fields(Ups, C)}; 397 | expand_expr({record_field,L,R,F}, C) -> %This is really illegal! 398 | {record_field,L,expand_expr(R, C),expand_expr(F, C)}; 399 | expand_expr({block,L,Es}, C) -> 400 | {block,L,expand_exprs(Es, C)}; 401 | expand_expr({'if',L,Cs}, C) -> 402 | {'if',L,expand_cs(Cs, C)}; 403 | expand_expr({'case',L,E,Cs}, C) -> 404 | {'case',L,expand_expr(E, C),expand_cs(Cs, C)}; 405 | expand_expr({'try',L,Es,Scs,Ccs,As}, C) -> 406 | {'try',L,expand_exprs(Es, C),expand_cs(Scs, C), 407 | expand_cs(Ccs, C),expand_exprs(As, C)}; 408 | expand_expr({'receive',L,Cs}, C) -> 409 | {'receive',L,expand_cs(Cs, C)}; 410 | expand_expr({'receive',L,Cs,To,ToEs}, C) -> 411 | {'receive',L,expand_cs(Cs, C), expand_expr(To, C), expand_exprs(ToEs, C)}; 412 | expand_expr({call,L,{atom,_,e},[N]}, C) -> 413 | case get_cmd(N, C) of 414 | {undefined,_,_} -> 415 | no_command(N); 416 | {[Ce],_V,_CommandN} -> 417 | Ce; 418 | {Ces,_V,_CommandN} when is_list(Ces) -> 419 | {block,L,Ces} 420 | end; 421 | expand_expr({call,_L,{atom,_,v},[N]}, C) -> 422 | case get_cmd(N, C) of 423 | {_,undefined,_} -> 424 | no_command(N); 425 | {Ces,V,CommandN} when is_list(Ces) -> 426 | {value,erl_anno:new(CommandN),V} 427 | end; 428 | expand_expr({call,L,F,Args}, C) -> 429 | {call,L,expand_expr(F, C),expand_exprs(Args, C)}; 430 | expand_expr({'catch',L,E}, C) -> 431 | {'catch',L,expand_expr(E, C)}; 432 | expand_expr({match,L,Lhs,Rhs}, C) -> 433 | {match,L,Lhs,expand_expr(Rhs, C)}; 434 | expand_expr({op,L,Op,Arg}, C) -> 435 | {op,L,Op,expand_expr(Arg, C)}; 436 | expand_expr({op,L,Op,Larg,Rarg}, C) -> 437 | {op,L,Op,expand_expr(Larg, C),expand_expr(Rarg, C)}; 438 | expand_expr({remote,L,M,F}, C) -> 439 | {remote,L,expand_expr(M, C),expand_expr(F, C)}; 440 | expand_expr({'fun',L,{clauses,Cs}}, C) -> 441 | {'fun',L,{clauses,expand_exprs(Cs, C)}}; 442 | expand_expr({named_fun,L,Name,Cs}, C) -> 443 | {named_fun,L,Name,expand_exprs(Cs, C)}; 444 | expand_expr({clause,L,H,G,B}, C) -> 445 | %% Could expand H and G, but then erl_eval has to be changed as well. 446 | {clause,L,H, G, expand_exprs(B, C)}; 447 | expand_expr({bin,L,Fs}, C) -> 448 | {bin,L,expand_bin_elements(Fs, C)}; 449 | expand_expr(E, _C) -> % Constants. 450 | E. 451 | 452 | expand_cs([{clause,L,P,G,B}|Cs], C) -> 453 | [{clause,L,P,G,expand_exprs(B, C)}|expand_cs(Cs, C)]; 454 | expand_cs([], _C) -> 455 | []. 456 | 457 | expand_fields([{record_field,L,F,V}|Fs], C) -> 458 | [{record_field,L,expand_expr(F, C),expand_expr(V, C)}| 459 | expand_fields(Fs, C)]; 460 | expand_fields([], _C) -> []. 461 | 462 | expand_quals([{generate,L,P,E}|Qs], C) -> 463 | [{generate,L,P,expand_expr(E, C)}|expand_quals(Qs, C)]; 464 | expand_quals([{b_generate,L,P,E}|Qs], C) -> 465 | [{b_generate,L,P,expand_expr(E, C)}|expand_quals(Qs, C)]; 466 | expand_quals([E|Qs], C) -> 467 | [expand_expr(E, C)|expand_quals(Qs, C)]; 468 | expand_quals([], _C) -> []. 469 | 470 | expand_bin_elements([], _C) -> 471 | []; 472 | expand_bin_elements([{bin_element,L,E,Sz,Ts}|Fs], C) -> 473 | [{bin_element,L,expand_expr(E, C),Sz,Ts}|expand_bin_elements(Fs, C)]. 474 | 475 | no_command(N) -> 476 | throw({error, 477 | io_lib:fwrite(<<"~ts: command not found">>, 478 | [erl_pp:expr(N, enc())])}). 479 | 480 | %% add_cmd(Number, Expressions, Value) 481 | %% get_cmd(Number, CurrentCommand) 482 | %% del_cmd(Number, NewN, OldN, HasBin0) -> bool() 483 | 484 | add_cmd(N, Es, V) -> 485 | put({command,N}, Es), 486 | put({result,N}, V). 487 | 488 | getc(N) -> 489 | {get({command,N}), get({result,N}), N}. 490 | 491 | get_cmd(Num, C) -> 492 | case catch erl_eval:expr(Num, erl_eval:new_bindings()) of 493 | {value,N,_} when N < 0 -> getc(C+N); 494 | {value,N,_} -> getc(N); 495 | _Other -> {undefined,undefined,undefined} 496 | end. 497 | 498 | del_cmd(_Type, N, N0, HasBin) when N < N0 -> 499 | HasBin; 500 | del_cmd(Type, N, N0, HasBin0) -> 501 | T = erase({Type,N}), 502 | HasBin = HasBin0 orelse has_binary(T), 503 | del_cmd(Type, N-1, N0, HasBin). 504 | 505 | has_binary(T) -> 506 | try has_bin(T), false 507 | catch true=Thrown -> Thrown 508 | end. 509 | 510 | has_bin(T) when is_tuple(T) -> 511 | has_bin(T, tuple_size(T)); 512 | has_bin([E | Es]) -> 513 | has_bin(E), 514 | has_bin(Es); 515 | has_bin(B) when byte_size(B) > ?MAXSIZE_HEAPBINARY -> 516 | throw(true); 517 | has_bin(T) -> 518 | T. 519 | 520 | has_bin(T, 0) -> 521 | T; 522 | has_bin(T, I) -> 523 | has_bin(element(I, T)), 524 | has_bin(T, I - 1). 525 | 526 | %% shell_cmd(Sequence, Evaluator, Bindings, RecordTable, Dictionary, What) 527 | %% shell_rep(Evaluator, Bindings, RecordTable, Dictionary) -> 528 | %% {Value,Evaluator,Bindings,Dictionary} 529 | %% Send a command to the evaluator and wait for the reply. Start a new 530 | %% evaluator if necessary. 531 | %% What = pmt | cmd. When evaluating a prompt ('pmt') the evaluated value 532 | %% must not be displayed, and it has to be returned. 533 | 534 | shell_cmd(Es, Eval, Bs, RT, Ds, W) -> 535 | Eval ! {shell_cmd,self(),{eval,Es}, W}, 536 | shell_rep(Eval, Bs, RT, Ds). 537 | 538 | shell_rep(Ev, Bs0, RT, Ds0) -> 539 | receive 540 | {shell_rep,Ev,{value,V,Bs,Ds}} -> 541 | {V,Ev,Bs,Ds}; 542 | {shell_rep,Ev,{command_error,{Line,M,Error}}} -> 543 | fwrite_severity(benign, <<"~w: ~ts">>, 544 | [Line, M:format_error(Error)]), 545 | {{'EXIT',Error},Ev,Bs0,Ds0}; 546 | {shell_req,Ev,get_cmd} -> 547 | Ev ! {shell_rep,self(),get()}, 548 | shell_rep(Ev, Bs0, RT, Ds0); 549 | {shell_req,Ev,exit} -> 550 | Ev ! {shell_rep,self(),exit}, 551 | exit(normal); 552 | {shell_req,Ev,{update_dict,Ds}} -> % Update dictionary 553 | Ev ! {shell_rep,self(),ok}, 554 | shell_rep(Ev, Bs0, RT, Ds); 555 | {ev_exit,{Ev,Class,Reason0}} -> % It has exited unnaturally 556 | receive {'EXIT',Ev,normal} -> ok end, 557 | report_exception(Class, Reason0, RT), 558 | Reason = nocatch(Class, Reason0), 559 | {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0}; 560 | {ev_caught,{Ev,Class,Reason0}} -> % catch_exception is in effect 561 | report_exception(Class, benign, Reason0, RT), 562 | Reason = nocatch(Class, Reason0), 563 | {{'EXIT',Reason},Ev,Bs0,Ds0}; 564 | {'EXIT',_Id,interrupt} -> % Someone interrupted us 565 | exit(Ev, kill), 566 | shell_rep(Ev, Bs0, RT, Ds0); 567 | {'EXIT',Ev,{Reason,Stacktrace}} -> 568 | report_exception(exit, {Reason,Stacktrace}, RT), 569 | {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0}; 570 | {'EXIT',Ev,Reason} -> 571 | report_exception(exit, {Reason,[]}, RT), 572 | {{'EXIT',Reason},start_eval(Bs0, RT, Ds0), Bs0, Ds0}; 573 | {'EXIT',_Id,R} -> 574 | exit(Ev, R), 575 | exit(R); 576 | _Other -> % Ignore everything else 577 | shell_rep(Ev, Bs0, RT, Ds0) 578 | end. 579 | 580 | nocatch(throw, {Term,Stack}) -> 581 | {{nocatch,Term},Stack}; 582 | nocatch(error, Reason) -> 583 | Reason; 584 | nocatch(exit, Reason) -> 585 | Reason. 586 | 587 | report_exception(Class, Reason, RT) -> 588 | report_exception(Class, serious, Reason, RT). 589 | 590 | report_exception(Class, Severity, {Reason,Stacktrace}, RT) -> 591 | Tag = severity_tag(Severity), 592 | I = iolist_size(Tag) + 1, 593 | PF = fun(Term, I1) -> pp(Term, I1, RT) end, 594 | SF = fun(M, _F, _A) -> (M =:= erl_eval) or (M =:= ?MODULE) end, 595 | Enc = encoding(), 596 | Str = lib:format_exception(I, Class, Reason, Stacktrace, SF, PF, Enc), 597 | io:requests([{put_chars, latin1, Tag}, 598 | {put_chars, unicode, Str}, 599 | nl]). 600 | 601 | start_eval(Bs, RT, Ds) -> 602 | Self = self(), 603 | Eval = spawn_link(fun() -> evaluator(Self, Bs, RT, Ds) end), 604 | put(evaluator, Eval), 605 | Eval. 606 | 607 | %% evaluator(Shell, Bindings, RecordTable, ProcessDictionary) 608 | %% Evaluate expressions from the shell. Use the "old" variable bindings 609 | %% and dictionary. 610 | 611 | evaluator(Shell, Bs, RT, Ds) -> 612 | init_dict(Ds), 613 | case application:get_env(stdlib, restricted_shell) of 614 | undefined -> 615 | eval_loop(Shell, Bs, RT); 616 | {ok,RShMod} -> 617 | case get(restricted_shell_state) of 618 | undefined -> put(restricted_shell_state, []); 619 | _ -> ok 620 | end, 621 | put(restricted_expr_state, []), 622 | restricted_eval_loop(Shell, Bs, RT, RShMod) 623 | end. 624 | 625 | eval_loop(Shell, Bs0, RT) -> 626 | receive 627 | {shell_cmd,Shell,{eval,Es},W} -> 628 | Ef = {value, 629 | fun(MForFun, As) -> apply_fun(MForFun, As, Shell) end}, 630 | Lf = local_func_handler(Shell, RT, Ef), 631 | Bs = eval_exprs(Es, Shell, Bs0, RT, Lf, Ef, W), 632 | eval_loop(Shell, Bs, RT) 633 | end. 634 | 635 | restricted_eval_loop(Shell, Bs0, RT, RShMod) -> 636 | receive 637 | {shell_cmd,Shell,{eval,Es}, W} -> 638 | {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT), 639 | put(restricted_expr_state, []), 640 | Bs = eval_exprs(Es, Shell, Bs0, RT, {eval,LFH}, {value,NLFH}, W), 641 | restricted_eval_loop(Shell, Bs, RT, RShMod) 642 | end. 643 | 644 | eval_exprs(Es, Shell, Bs0, RT, Lf, Ef, W) -> 645 | try 646 | {R,Bs2} = exprs(Es, Bs0, RT, Lf, Ef, W), 647 | Shell ! {shell_rep,self(),R}, 648 | Bs2 649 | catch 650 | exit:normal -> 651 | exit(normal); 652 | Class:Reason -> 653 | Stacktrace = erlang:get_stacktrace(), 654 | M = {self(),Class,{Reason,Stacktrace}}, 655 | case do_catch(Class, Reason) of 656 | true -> 657 | Shell ! {ev_caught,M}, 658 | Bs0; 659 | false -> 660 | %% We don't want the ERROR REPORT generated by the 661 | %% emulator. Note: exit(kill) needs nothing special. 662 | {links,LPs} = process_info(self(), links), 663 | ER = nocatch(Class, {Reason,Stacktrace}), 664 | lists:foreach(fun(P) -> exit(P, ER) end, LPs--[Shell]), 665 | Shell ! {ev_exit,M}, 666 | exit(normal) 667 | end 668 | end. 669 | 670 | do_catch(exit, restricted_shell_stopped) -> 671 | false; 672 | do_catch(exit, restricted_shell_started) -> 673 | false; 674 | do_catch(_Class, _Reason) -> 675 | case application:get_env(stdlib, shell_catch_exception) of 676 | {ok, true} -> 677 | true; 678 | _ -> 679 | false 680 | end. 681 | 682 | exprs(Es, Bs0, RT, Lf, Ef, W) -> 683 | exprs(Es, Bs0, RT, Lf, Ef, Bs0, W). 684 | 685 | exprs([E0|Es], Bs1, RT, Lf, Ef, Bs0, W) -> 686 | UsedRecords = used_record_defs(E0, RT), 687 | RBs = record_bindings(UsedRecords, Bs1), 688 | case check_command(prep_check([E0]), RBs) of 689 | ok -> 690 | E1 = expand_records(UsedRecords, E0), 691 | {value,V0,Bs2} = expr(E1, Bs1, Lf, Ef), 692 | Bs = orddict:from_list([VV || {X,_}=VV <- erl_eval:bindings(Bs2), 693 | not is_expand_variable(X)]), 694 | if 695 | Es =:= [] -> 696 | VS = pp(V0, 1, RT), 697 | case W of 698 | cmd -> io:requests([{put_chars, unicode, VS}, nl]); 699 | pmt -> ok 700 | end, 701 | %% Don't send the result back if it will be 702 | %% discarded anyway. 703 | V = if 704 | W =:= pmt -> 705 | {W,V0}; 706 | true -> case result_will_be_saved() of 707 | true -> V0; 708 | false -> ignored 709 | end 710 | end, 711 | {{value,V,Bs,get()},Bs}; 712 | true -> 713 | exprs(Es, Bs, RT, Lf, Ef, Bs0, W) 714 | end; 715 | {error,Error} -> 716 | {{command_error,Error},Bs0} 717 | end. 718 | 719 | is_expand_variable(V) -> 720 | case catch atom_to_list(V) of 721 | "rec" ++ _Integer -> true; 722 | _ -> false 723 | end. 724 | 725 | result_will_be_saved() -> 726 | case get_history_and_results() of 727 | {_, 0} -> false; 728 | _ -> true 729 | end. 730 | 731 | used_record_defs(E, RT) -> 732 | %% Be careful to return a list where used records come before 733 | %% records that use them. The linter wants them ordered that way. 734 | UR = case used_records(E, [], RT) of 735 | [] -> 736 | []; 737 | L0 -> 738 | L1 = lists:zip(L0, lists:seq(1, length(L0))), 739 | L2 = lists:keysort(2, lists:ukeysort(1, L1)), 740 | [R || {R, _} <- L2] 741 | end, 742 | record_defs(RT, UR). 743 | 744 | used_records(E, U0, RT) -> 745 | case used_records(E) of 746 | {name,Name,E1} -> 747 | U = used_records(ets:lookup(RT, Name), [Name | U0], RT), 748 | used_records(E1, U, RT); 749 | {expr,[E1 | Es]} -> 750 | used_records(Es, used_records(E1, U0, RT), RT); 751 | _ -> 752 | U0 753 | end. 754 | 755 | used_records({record_index,_,Name,F}) -> 756 | {name, Name, F}; 757 | used_records({record,_,Name,Is}) -> 758 | {name, Name, Is}; 759 | used_records({record_field,_,R,Name,F}) -> 760 | {name, Name, [R | F]}; 761 | used_records({record,_,R,Name,Ups}) -> 762 | {name, Name, [R | Ups]}; 763 | used_records({record_field,_,R,F}) -> % illegal 764 | {expr, [R | F]}; 765 | used_records({call,_,{atom,_,record},[A,{atom,_,Name}]}) -> 766 | {name, Name, A}; 767 | used_records({call,_,{atom,_,is_record},[A,{atom,_,Name}]}) -> 768 | {name, Name, A}; 769 | used_records({call,_,{remote,_,{atom,_,erlang},{atom,_,is_record}}, 770 | [A,{atom,_,Name}]}) -> 771 | {name, Name, A}; 772 | used_records({call,_,{atom,_,record_info},[A,{atom,_,Name}]}) -> 773 | {name, Name, A}; 774 | used_records({call,Line,{tuple,_,[M,F]},As}) -> 775 | used_records({call,Line,{remote,Line,M,F},As}); 776 | used_records({type,_,record,[{atom,_,Name}|Fs]}) -> 777 | {name, Name, Fs}; 778 | used_records(T) when is_tuple(T) -> 779 | {expr, tuple_to_list(T)}; 780 | used_records(E) -> 781 | {expr, E}. 782 | 783 | fwrite_severity(Severity, S, As) -> 784 | io:fwrite(<<"~ts\n">>, [format_severity(Severity, S, As)]). 785 | 786 | format_severity(Severity, S, As) -> 787 | add_severity(Severity, io_lib:fwrite(S, As)). 788 | 789 | add_severity(Severity, S) -> 790 | [severity_tag(Severity), S]. 791 | 792 | severity_tag(fatal) -> <<"*** ">>; 793 | severity_tag(serious) -> <<"** ">>; 794 | severity_tag(benign) -> <<"* ">>. 795 | 796 | restrict_handlers(RShMod, Shell, RT) -> 797 | { fun(F,As,Binds) -> 798 | local_allowed(F, As, RShMod, Binds, Shell, RT) 799 | end, 800 | fun(MF,As) -> 801 | non_local_allowed(MF, As, RShMod, Shell) 802 | end }. 803 | 804 | -define(BAD_RETURN(M, F, V), 805 | try erlang:error(reason) 806 | catch _:_ -> erlang:raise(exit, {restricted_shell_bad_return,V}, 807 | [{M,F,3} | erlang:get_stacktrace()]) 808 | end). 809 | 810 | local_allowed(F, As, RShMod, Bs, Shell, RT) when is_atom(F) -> 811 | {LFH,NLFH} = restrict_handlers(RShMod, Shell, RT), 812 | case not_restricted(F, As) of % Not restricted is the same as builtin. 813 | % variable and record manipulations local 814 | % to the shell process. Those are never 815 | % restricted. 816 | true -> 817 | local_func(F, As, Bs, Shell, RT, {eval,LFH}, {value,NLFH}); 818 | false -> 819 | {AsEv,Bs1} = expr_list(As, Bs, {eval,LFH}, {value,NLFH}), 820 | case RShMod:local_allowed(F, AsEv, {get(restricted_shell_state), 821 | get(restricted_expr_state)}) of 822 | {Result,{RShShSt,RShExprSt}} -> 823 | put(restricted_shell_state, RShShSt), 824 | put(restricted_expr_state, RShExprSt), 825 | if not Result -> 826 | shell_req(Shell, {update_dict,get()}), 827 | exit({restricted_shell_disallowed,{F,AsEv}}); 828 | true -> % This is never a builtin, 829 | % those are handled above. 830 | non_builtin_local_func(F,AsEv,Bs1) 831 | end; 832 | Unexpected -> % The user supplied non conforming module 833 | ?BAD_RETURN(RShMod, local_allowed, Unexpected) 834 | end 835 | end. 836 | 837 | non_local_allowed(MForFun, As, RShMod, Shell) -> 838 | case RShMod:non_local_allowed(MForFun, As, {get(restricted_shell_state), 839 | get(restricted_expr_state)}) of 840 | {Result,{RShShSt,RShExprSt}} -> 841 | put(restricted_shell_state, RShShSt), 842 | put(restricted_expr_state, RShExprSt), 843 | case Result of 844 | false -> 845 | shell_req(Shell, {update_dict,get()}), 846 | exit({restricted_shell_disallowed,{MForFun,As}}); 847 | {redirect, NewMForFun, NewAs} -> 848 | apply_fun(NewMForFun, NewAs, Shell); 849 | _ -> 850 | apply_fun(MForFun, As, Shell) 851 | end; 852 | Unexpected -> % The user supplied non conforming module 853 | ?BAD_RETURN(RShMod, non_local_allowed, Unexpected) 854 | end. 855 | 856 | %% The commands implemented in shell should not be checked if allowed 857 | %% This *has* to correspond to the function local_func/7! 858 | %% (especially true for f/1, the argument must not be evaluated). 859 | not_restricted(f, []) -> 860 | true; 861 | not_restricted(f, [_]) -> 862 | true; 863 | not_restricted(h, []) -> 864 | true; 865 | not_restricted(b, []) -> 866 | true; 867 | not_restricted(history, [_]) -> 868 | true; 869 | not_restricted(results, [_]) -> 870 | true; 871 | not_restricted(catch_exception, [_]) -> 872 | true; 873 | not_restricted(exit, []) -> 874 | true; 875 | not_restricted(rd, [_,_]) -> 876 | true; 877 | not_restricted(rf, []) -> 878 | true; 879 | not_restricted(rf, [_]) -> 880 | true; 881 | not_restricted(rl, []) -> 882 | true; 883 | not_restricted(rl, [_]) -> 884 | true; 885 | not_restricted(rp, [_]) -> 886 | true; 887 | not_restricted(rr, [_]) -> 888 | true; 889 | not_restricted(rr, [_,_]) -> 890 | true; 891 | not_restricted(rr, [_,_,_]) -> 892 | true; 893 | not_restricted(_, _) -> 894 | false. 895 | 896 | %% When erlang:garbage_collect() is called from the shell, 897 | %% the shell process process that spawned the evaluating 898 | %% process is garbage collected as well. 899 | %% To garbage collect the evaluating process only the command 900 | %% garbage_collect(self()). can be used. 901 | apply_fun({erlang,garbage_collect}, [], Shell) -> 902 | garb(Shell); 903 | apply_fun({M,F}, As, _Shell) -> 904 | apply(M, F, As); 905 | apply_fun(MForFun, As, _Shell) -> 906 | apply(MForFun, As). 907 | 908 | prep_check({call,Line,{atom,_,f},[{var,_,_Name}]}) -> 909 | %% Do not emit a warning for f(V) when V is unbound. 910 | {atom,Line,ok}; 911 | prep_check({value,_CommandN,_Val}) -> 912 | %% erl_lint cannot handle the history expansion {value,_,_}. 913 | {atom,a0(),ok}; 914 | prep_check(T) when is_tuple(T) -> 915 | list_to_tuple(prep_check(tuple_to_list(T))); 916 | prep_check([E | Es]) -> 917 | [prep_check(E) | prep_check(Es)]; 918 | prep_check(E) -> 919 | E. 920 | 921 | expand_records([], E0) -> 922 | E0; 923 | expand_records(UsedRecords, E0) -> 924 | RecordDefs = [Def || {_Name,Def} <- UsedRecords], 925 | L = erl_anno:new(1), 926 | E = prep_rec(E0), 927 | Forms0 = RecordDefs ++ [{function,L,foo,0,[{clause,L,[],[],[E]}]}], 928 | Forms = erl_expand_records:module(Forms0, [strict_record_tests]), 929 | {function,L,foo,0,[{clause,L,[],[],[NE]}]} = lists:last(Forms), 930 | prep_rec(NE). 931 | 932 | prep_rec({value,_CommandN,_V}=Value) -> 933 | %% erl_expand_records cannot handle the history expansion {value,_,_}. 934 | {atom,Value,ok}; 935 | prep_rec({atom,{value,_CommandN,_V}=Value,ok}) -> 936 | %% Undo the effect of the previous clause... 937 | Value; 938 | prep_rec(T) when is_tuple(T) -> list_to_tuple(prep_rec(tuple_to_list(T))); 939 | prep_rec([E | Es]) -> [prep_rec(E) | prep_rec(Es)]; 940 | prep_rec(E) -> E. 941 | 942 | init_dict([{K,V}|Ds]) -> 943 | put(K, V), 944 | init_dict(Ds); 945 | init_dict([]) -> true. 946 | 947 | %% local_func(Function, Args, Bindings, Shell, RecordTable, 948 | %% LocalFuncHandler, ExternalFuncHandler) -> {value,Val,Bs} 949 | %% Evaluate local functions, including shell commands. 950 | %% 951 | %% Note that the predicate not_restricted/2 has to correspond to what's 952 | %% handled internally - it should return 'true' for all local functions 953 | %% handled in this module (i.e. those that are not eventually handled by 954 | %% non_builtin_local_func/3 (user_default/shell_default). 955 | 956 | local_func(h, [], Bs, Shell, RT, _Lf, _Ef) -> 957 | Cs = shell_req(Shell, get_cmd), 958 | Cs1 = lists:filter(fun({{command, _},_}) -> true; 959 | ({{result, _},_}) -> true; 960 | (_) -> false 961 | end, 962 | Cs), 963 | Cs2 = lists:map(fun({{T, N}, V}) -> {{N, T}, V} end, 964 | Cs1), 965 | Cs3 = lists:keysort(1, Cs2), 966 | {value,list_commands(Cs3, RT),Bs}; 967 | local_func(b, [], Bs, _Shell, RT, _Lf, _Ef) -> 968 | {value,list_bindings(erl_eval:bindings(Bs), RT),Bs}; 969 | local_func(f, [], _Bs, _Shell, _RT, _Lf, _Ef) -> 970 | {value,ok,erl_eval:new_bindings()}; 971 | local_func(f, [{var,_,Name}], Bs, _Shell, _RT, _Lf, _Ef) -> 972 | {value,ok,erl_eval:del_binding(Name, Bs)}; 973 | local_func(f, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) -> 974 | erlang:raise(error, function_clause, [{shell,f,1}]); 975 | local_func(rd, [{atom,_,RecName0},RecDef0], Bs, _Shell, RT, _Lf, _Ef) -> 976 | RecDef = expand_value(RecDef0), 977 | RDs = lists:flatten(erl_pp:expr(RecDef)), 978 | RecName = io_lib:write_atom_as_latin1(RecName0), 979 | Attr = lists:concat(["-record(", RecName, ",", RDs, ")."]), 980 | {ok, Tokens, _} = erl_scan:string(Attr), 981 | case erl_parse:parse_form(Tokens) of 982 | {ok,AttrForm} -> 983 | [RN] = add_records([AttrForm], Bs, RT), 984 | {value,RN,Bs}; 985 | {error,{_Line,M,ErrDesc}} -> 986 | ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]), 987 | exit(lists:flatten(ErrStr)) 988 | end; 989 | local_func(rd, [_,_], _Bs, _Shell, _RT, _Lf, _Ef) -> 990 | erlang:raise(error, function_clause, [{shell,rd,2}]); 991 | local_func(rf, [], Bs, _Shell, RT, _Lf, _Ef) -> 992 | true = ets:delete_all_objects(RT), 993 | {value,initiate_records(Bs, RT),Bs}; 994 | local_func(rf, [A], Bs0, _Shell, RT, Lf, Ef) -> 995 | {[Recs],Bs} = expr_list([A], Bs0, Lf, Ef), 996 | if '_' =:= Recs -> 997 | true = ets:delete_all_objects(RT); 998 | true -> 999 | lists:foreach(fun(Name) -> true = ets:delete(RT, Name) 1000 | end, listify(Recs)) 1001 | end, 1002 | {value,ok,Bs}; 1003 | local_func(rl, [], Bs, _Shell, RT, _Lf, _Ef) -> 1004 | {value,list_records(ets:tab2list(RT)),Bs}; 1005 | local_func(rl, [A], Bs0, _Shell, RT, Lf, Ef) -> 1006 | {[Recs],Bs} = expr_list([A], Bs0, Lf, Ef), 1007 | {value,list_records(record_defs(RT, listify(Recs))),Bs}; 1008 | local_func(rp, [A], Bs0, _Shell, RT, Lf, Ef) -> 1009 | {[V],Bs} = expr_list([A], Bs0, Lf, Ef), 1010 | Cs = pp(V, _Column=1, _Depth=-1, RT), 1011 | io:requests([{put_chars, unicode, Cs}, nl]), 1012 | {value,ok,Bs}; 1013 | local_func(rr, [A], Bs0, _Shell, RT, Lf, Ef) -> 1014 | {[File],Bs} = expr_list([A], Bs0, Lf, Ef), 1015 | {value,read_and_add_records(File, '_', [], Bs, RT),Bs}; 1016 | local_func(rr, [_,_]=As0, Bs0, _Shell, RT, Lf, Ef) -> 1017 | {[File,Sel],Bs} = expr_list(As0, Bs0, Lf, Ef), 1018 | {value,read_and_add_records(File, Sel, [], Bs, RT),Bs}; 1019 | local_func(rr, [_,_,_]=As0, Bs0, _Shell, RT, Lf, Ef) -> 1020 | {[File,Sel,Options],Bs} = expr_list(As0, Bs0, Lf, Ef), 1021 | {value,read_and_add_records(File, Sel, Options, Bs, RT),Bs}; 1022 | local_func(history, [{integer,_,N}], Bs, _Shell, _RT, _Lf, _Ef) -> 1023 | {value,history(N),Bs}; 1024 | local_func(history, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) -> 1025 | erlang:raise(error, function_clause, [{shell,history,1}]); 1026 | local_func(results, [{integer,_,N}], Bs, _Shell, _RT, _Lf, _Ef) -> 1027 | {value,results(N),Bs}; 1028 | local_func(results, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) -> 1029 | erlang:raise(error, function_clause, [{shell,results,1}]); 1030 | local_func(catch_exception, [{atom,_,Bool}], Bs, _Shell, _RT, _Lf, _Ef) 1031 | when Bool; not Bool -> 1032 | {value,catch_exception(Bool),Bs}; 1033 | local_func(catch_exception, [_Other], _Bs, _Shell, _RT, _Lf, _Ef) -> 1034 | erlang:raise(error, function_clause, [{shell,catch_exception,1}]); 1035 | local_func(exit, [], _Bs, Shell, _RT, _Lf, _Ef) -> 1036 | shell_req(Shell, exit), %This terminates us 1037 | exit(normal); 1038 | local_func(F, As0, Bs0, _Shell, _RT, Lf, Ef) when is_atom(F) -> 1039 | {As,Bs} = expr_list(As0, Bs0, Lf, Ef), 1040 | non_builtin_local_func(F,As,Bs). 1041 | 1042 | non_builtin_local_func(F,As,Bs) -> 1043 | Arity = length(As), 1044 | case erlang:function_exported(user_default, F, Arity) of 1045 | true -> 1046 | {eval,erlang:make_fun(user_default, F, Arity),As,Bs}; 1047 | false -> 1048 | shell_default(F,As,Bs) 1049 | end. 1050 | 1051 | shell_default(F,As,Bs) -> 1052 | M = shell_default, 1053 | A = length(As), 1054 | case code:ensure_loaded(M) of 1055 | {module, _} -> 1056 | case erlang:function_exported(M,F,A) of 1057 | true -> 1058 | {eval,erlang:make_fun(M, F, A),As,Bs}; 1059 | false -> 1060 | shell_undef(F,A) 1061 | end; 1062 | {error, _} -> 1063 | shell_undef(F,A) 1064 | end. 1065 | 1066 | shell_undef(F,A) -> 1067 | erlang:error({shell_undef,F,A,[]}). 1068 | 1069 | local_func_handler(Shell, RT, Ef) -> 1070 | H = fun(Lf) -> 1071 | fun(F, As, Bs) -> 1072 | local_func(F, As, Bs, Shell, RT, {eval,Lf(Lf)}, Ef) 1073 | end 1074 | end, 1075 | {eval,H(H)}. 1076 | 1077 | record_print_fun(RT) -> 1078 | fun(Tag, NoFields) -> 1079 | case ets:lookup(RT, Tag) of 1080 | [{_,{attribute,_,record,{Tag,Fields}}}] 1081 | when length(Fields) =:= NoFields -> 1082 | record_fields(Fields); 1083 | _ -> 1084 | no 1085 | end 1086 | end. 1087 | 1088 | record_fields([{record_field,_,{atom,_,Field}} | Fs]) -> 1089 | [Field | record_fields(Fs)]; 1090 | record_fields([{record_field,_,{atom,_,Field},_} | Fs]) -> 1091 | [Field | record_fields(Fs)]; 1092 | record_fields([{typed_record_field,Field,_Type} | Fs]) -> 1093 | record_fields([Field | Fs]); 1094 | record_fields([]) -> 1095 | []. 1096 | 1097 | initiate_records(Bs, RT) -> 1098 | RNs1 = init_rec(shell_default, Bs, RT), 1099 | RNs2 = case code:is_loaded(user_default) of 1100 | {file,_File} -> 1101 | init_rec(user_default, Bs, RT); 1102 | false -> 1103 | [] 1104 | end, 1105 | lists:usort(RNs1 ++ RNs2). 1106 | 1107 | init_rec(Module, Bs, RT) -> 1108 | case read_records(Module, []) of 1109 | RAs when is_list(RAs) -> 1110 | case catch add_records(RAs, Bs, RT) of 1111 | {'EXIT',_} -> 1112 | []; 1113 | RNs -> 1114 | RNs 1115 | end; 1116 | _Error -> 1117 | [] 1118 | end. 1119 | 1120 | read_and_add_records(File, Selected, Options, Bs, RT) -> 1121 | case read_records(File, Selected, Options) of 1122 | RAs when is_list(RAs) -> 1123 | add_records(RAs, Bs, RT); 1124 | Error -> 1125 | Error 1126 | end. 1127 | 1128 | read_records(File, Selected, Options) -> 1129 | case read_records(File, listify(Options)) of 1130 | Error when is_tuple(Error) -> 1131 | Error; 1132 | RAs when Selected =:= '_' -> 1133 | RAs; 1134 | RAs -> 1135 | Sel = listify(Selected), 1136 | [RA || {attribute,_,_,{Name,_}}=RA <- RAs, 1137 | lists:member(Name, Sel)] 1138 | end. 1139 | 1140 | add_records(RAs, Bs0, RT) -> 1141 | Recs = [{Name,D} || {attribute,_,_,{Name,_}}=D <- RAs], 1142 | Bs1 = record_bindings(Recs, Bs0), 1143 | case check_command([], Bs1) of 1144 | {error,{_Line,M,ErrDesc}} -> 1145 | %% A source file that has not been compiled. 1146 | ErrStr = io_lib:fwrite(<<"~ts">>, [M:format_error(ErrDesc)]), 1147 | exit(lists:flatten(ErrStr)); 1148 | ok -> 1149 | true = ets:insert(RT, Recs), 1150 | lists:usort([Name || {Name,_} <- Recs]) 1151 | end. 1152 | 1153 | listify(L) when is_list(L) -> 1154 | L; 1155 | listify(E) -> 1156 | [E]. 1157 | 1158 | check_command(Es, Bs) -> 1159 | erl_eval:check_command(Es, Bs). 1160 | 1161 | expr(E, Bs, Lf, Ef) -> 1162 | erl_eval:expr(E, Bs, Lf, Ef). 1163 | 1164 | expr_list(Es, Bs, Lf, Ef) -> 1165 | erl_eval:expr_list(Es, Bs, Lf, Ef). 1166 | 1167 | %% Note that a sequence number is used here to make sure that if a 1168 | %% record is used by another record, then the first record is parsed 1169 | %% before the second record. (erl_eval:check_command() calls the 1170 | %% linter which needs the records in a proper order.) 1171 | record_bindings([], Bs) -> 1172 | Bs; 1173 | record_bindings(Recs0, Bs0) -> 1174 | {Recs1, _} = lists:mapfoldl(fun ({Name,Def}, I) -> {{Name,I,Def},I+1} 1175 | end, 0, Recs0), 1176 | Recs2 = lists:keysort(2, lists:ukeysort(1, Recs1)), 1177 | lists:foldl(fun ({Name,I,Def}, Bs) -> 1178 | erl_eval:add_binding({record,I,Name}, Def, Bs) 1179 | end, Bs0, Recs2). 1180 | 1181 | %%% Read record information from file(s) 1182 | 1183 | read_records(FileOrModule, Opts0) -> 1184 | Opts = lists:delete(report_warnings, Opts0), 1185 | case find_file(FileOrModule) of 1186 | {files,[File]} -> 1187 | read_file_records(File, Opts); 1188 | {files,Files} -> 1189 | lists:flatmap(fun(File) -> 1190 | case read_file_records(File, Opts) of 1191 | RAs when is_list(RAs) -> RAs; 1192 | _ -> [] 1193 | end 1194 | end, Files); 1195 | Error -> 1196 | Error 1197 | end. 1198 | 1199 | -include_lib("kernel/include/file.hrl"). 1200 | 1201 | find_file(Mod) when is_atom(Mod) -> 1202 | case code:which(Mod) of 1203 | File when is_list(File) -> 1204 | {files,[File]}; 1205 | preloaded -> 1206 | {_M,_Bin,File} = code:get_object_code(Mod), 1207 | {files,[File]}; 1208 | _Else -> % non_existing, interpreted, cover_compiled 1209 | {error,nofile} 1210 | end; 1211 | find_file(File) -> 1212 | case catch filelib:wildcard(File) of 1213 | {'EXIT',_} -> 1214 | {error,invalid_filename}; 1215 | Files -> 1216 | {files,Files} 1217 | end. 1218 | 1219 | read_file_records(File, Opts) -> 1220 | case filename:extension(File) of 1221 | ".beam" -> 1222 | case beam_lib:chunks(File, [abstract_code,"CInf"]) of 1223 | {ok,{_Mod,[{abstract_code,{Version,Forms}},{"CInf",CB}]}} -> 1224 | case record_attrs(Forms) of 1225 | [] when Version =:= raw_abstract_v1 -> 1226 | []; 1227 | [] -> 1228 | %% If the version is raw_X, then this test 1229 | %% is unnecessary. 1230 | try_source(File, CB); 1231 | Records -> 1232 | Records 1233 | end; 1234 | {ok,{_Mod,[{abstract_code,no_abstract_code},{"CInf",CB}]}} -> 1235 | try_source(File, CB); 1236 | Error -> 1237 | %% Could be that the "Abst" chunk is missing (pre R6). 1238 | Error 1239 | end; 1240 | _ -> 1241 | parse_file(File, Opts) 1242 | end. 1243 | 1244 | %% This is how the debugger searches for source files. See int.erl. 1245 | try_source(Beam, RawCB) -> 1246 | EbinDir = filename:dirname(Beam), 1247 | CB = binary_to_term(RawCB), 1248 | Os = proplists:get_value(options,CB, []), 1249 | Src0 = filename:rootname(Beam) ++ ".erl", 1250 | Src1 = filename:join([filename:dirname(EbinDir), "src", 1251 | filename:basename(Src0)]), 1252 | Src2 = proplists:get_value(source, CB, []), 1253 | try_sources([Src0,Src1,Src2], Os). 1254 | 1255 | try_sources([], _) -> 1256 | {error, nofile}; 1257 | try_sources([Src|Rest], Os) -> 1258 | case is_file(Src) of 1259 | true -> parse_file(Src, Os); 1260 | false -> try_sources(Rest, Os) 1261 | end. 1262 | 1263 | is_file(Name) -> 1264 | case filelib:is_file(Name) of 1265 | true -> 1266 | not filelib:is_dir(Name); 1267 | false -> 1268 | false 1269 | end. 1270 | 1271 | parse_file(File, Opts) -> 1272 | Cwd = ".", 1273 | Dir = filename:dirname(File), 1274 | IncludePath = [Cwd,Dir|inc_paths(Opts)], 1275 | case epp:parse_file(File, IncludePath, pre_defs(Opts)) of 1276 | {ok,Forms} -> 1277 | record_attrs(Forms); 1278 | Error -> 1279 | Error 1280 | end. 1281 | 1282 | pre_defs([{d,M,V}|Opts]) -> 1283 | [{M,V}|pre_defs(Opts)]; 1284 | pre_defs([{d,M}|Opts]) -> 1285 | [M|pre_defs(Opts)]; 1286 | pre_defs([_|Opts]) -> 1287 | pre_defs(Opts); 1288 | pre_defs([]) -> []. 1289 | 1290 | inc_paths(Opts) -> 1291 | [P || {i,P} <- Opts, is_list(P)]. 1292 | 1293 | record_attrs(Forms) -> 1294 | [A || A = {attribute,_,record,_D} <- Forms]. 1295 | 1296 | %%% End of reading record information from file(s) 1297 | 1298 | shell_req(Shell, Req) -> 1299 | Shell ! {shell_req,self(),Req}, 1300 | receive 1301 | {shell_rep,Shell,Rep} -> Rep 1302 | end. 1303 | 1304 | list_commands([{{N,command},Es0}, {{N,result}, V} |Ds], RT) -> 1305 | Es = prep_list_commands(Es0), 1306 | VS = pp(V, 4, RT), 1307 | Ns = io_lib:fwrite(<<"~w: ">>, [N]), 1308 | I = iolist_size(Ns), 1309 | io:requests([{put_chars, latin1, Ns}, 1310 | {format,<<"~ts\n">>,[erl_pp:exprs(Es, I, enc())]}, 1311 | {format,<<"-> ">>,[]}, 1312 | {put_chars, unicode, VS}, 1313 | nl]), 1314 | list_commands(Ds, RT); 1315 | list_commands([{{N,command},Es0} |Ds], RT) -> 1316 | Es = prep_list_commands(Es0), 1317 | Ns = io_lib:fwrite(<<"~w: ">>, [N]), 1318 | I = iolist_size(Ns), 1319 | io:requests([{put_chars, latin1, Ns}, 1320 | {format,<<"~ts\n">>,[erl_pp:exprs(Es, I, enc())]}]), 1321 | list_commands(Ds, RT); 1322 | list_commands([_D|Ds], RT) -> 1323 | list_commands(Ds, RT); 1324 | list_commands([], _RT) -> ok. 1325 | 1326 | list_bindings([{Name,Val}|Bs], RT) -> 1327 | case erl_eval:fun_data(Val) of 1328 | {fun_data,_FBs,FCs0} -> 1329 | FCs = expand_value(FCs0), % looks nicer 1330 | A = a0(), 1331 | F = {'fun',A,{clauses,FCs}}, 1332 | M = {match,A,{var,A,Name},F}, 1333 | io:fwrite(<<"~ts\n">>, [erl_pp:expr(M, enc())]); 1334 | {named_fun_data,_FBs,FName,FCs0} -> 1335 | FCs = expand_value(FCs0), % looks nicer 1336 | A = a0(), 1337 | F = {named_fun,A,FName,FCs}, 1338 | M = {match,A,{var,A,Name},F}, 1339 | io:fwrite(<<"~ts\n">>, [erl_pp:expr(M, enc())]); 1340 | false -> 1341 | Namel = io_lib:fwrite(<<"~s = ">>, [Name]), 1342 | Nl = iolist_size(Namel)+1, 1343 | ValS = pp(Val, Nl, RT), 1344 | io:requests([{put_chars, latin1, Namel}, 1345 | {put_chars, unicode, ValS}, 1346 | nl]) 1347 | end, 1348 | list_bindings(Bs, RT); 1349 | list_bindings([], _RT) -> 1350 | ok. 1351 | 1352 | list_records(Records) -> 1353 | lists:foreach(fun({_Name,Attr}) -> 1354 | io:fwrite(<<"~ts">>, [erl_pp:attribute(Attr, enc())]) 1355 | end, Records). 1356 | 1357 | record_defs(RT, Names) -> 1358 | lists:flatmap(fun(Name) -> ets:lookup(RT, Name) 1359 | end, Names). 1360 | 1361 | expand_value(E) -> 1362 | substitute_v1(fun({value,CommandN,V}) -> try_abstract(V, CommandN) 1363 | end, E). 1364 | 1365 | %% There is no abstract representation of funs. 1366 | try_abstract(V, CommandN) -> 1367 | try erl_parse:abstract(V) 1368 | catch 1369 | _:_ -> 1370 | A = a0(), 1371 | {call,A,{atom,A,v},[{integer,A,CommandN}]} 1372 | end. 1373 | 1374 | %% Rather than listing possibly huge results the calls to v/1 are shown. 1375 | prep_list_commands(E) -> 1376 | A = a0(), 1377 | substitute_v1(fun({value,Anno,_V}) -> 1378 | CommandN = erl_anno:line(Anno), 1379 | {call,A,{atom,A,v},[{integer,A,CommandN}]} 1380 | end, E). 1381 | 1382 | substitute_v1(F, {value,_,_}=Value) -> 1383 | F(Value); 1384 | substitute_v1(F, T) when is_tuple(T) -> 1385 | list_to_tuple(substitute_v1(F, tuple_to_list(T))); 1386 | substitute_v1(F, [E | Es]) -> 1387 | [substitute_v1(F, E) | substitute_v1(F, Es)]; 1388 | substitute_v1(_F, E) -> 1389 | E. 1390 | 1391 | a0() -> 1392 | erl_anno:new(0). 1393 | 1394 | check_and_get_history_and_results() -> 1395 | check_env(shell_history_length), 1396 | check_env(shell_saved_results), 1397 | get_history_and_results(). 1398 | 1399 | get_history_and_results() -> 1400 | History = get_env(shell_history_length, ?DEF_HISTORY), 1401 | Results = get_env(shell_saved_results, ?DEF_RESULTS), 1402 | {History, erlang:min(Results, History)}. 1403 | 1404 | pp(V, I, RT) -> 1405 | pp(V, I, _Depth=?LINEMAX, RT). 1406 | 1407 | pp(V, I, D, RT) -> 1408 | Strings = 1409 | case application:get_env(stdlib, shell_strings) of 1410 | {ok, false} -> 1411 | false; 1412 | _ -> 1413 | true 1414 | end, 1415 | io_lib_pretty:print(V, ([{column, I}, {line_length, columns()}, 1416 | {depth, D}, {max_chars, ?CHAR_MAX}, 1417 | {strings, Strings}, 1418 | {record_print_fun, record_print_fun(RT)}] 1419 | ++ enc())). 1420 | 1421 | columns() -> 1422 | case io:columns() of 1423 | {ok,N} -> N; 1424 | _ -> 80 1425 | end. 1426 | 1427 | encoding() -> 1428 | [{encoding, Encoding}] = enc(), 1429 | Encoding. 1430 | 1431 | enc() -> 1432 | case lists:keyfind(encoding, 1, io:getopts()) of 1433 | false -> [{encoding,latin1}]; % should never happen 1434 | Enc -> [Enc] 1435 | end. 1436 | 1437 | garb(Shell) -> 1438 | erlang:garbage_collect(Shell), 1439 | catch erlang:garbage_collect(whereis(user)), 1440 | catch erlang:garbage_collect(group_leader()), 1441 | erlang:garbage_collect(). 1442 | 1443 | get_env(V, Def) -> 1444 | case application:get_env(stdlib, V) of 1445 | {ok, Val} when is_integer(Val), Val >= 0 -> 1446 | Val; 1447 | _ -> 1448 | Def 1449 | end. 1450 | 1451 | check_env(V) -> 1452 | case application:get_env(stdlib, V) of 1453 | undefined -> 1454 | ok; 1455 | {ok, Val} when is_integer(Val), Val >= 0 -> 1456 | ok; 1457 | {ok, Val} -> 1458 | Txt = io_lib:fwrite 1459 | ("Invalid value of STDLIB configuration parameter" 1460 | "~w: ~tp\n", [V, Val]), 1461 | error_logger:info_report(lists:flatten(Txt)) 1462 | end. 1463 | 1464 | set_env(App, Name, Val, Default) -> 1465 | Prev = case application:get_env(App, Name) of 1466 | undefined -> 1467 | Default; 1468 | {ok, Old} -> 1469 | Old 1470 | end, 1471 | application_controller:set_env(App, Name, Val), 1472 | Prev. 1473 | 1474 | -spec history(N) -> non_neg_integer() when 1475 | N :: non_neg_integer(). 1476 | 1477 | history(L) when is_integer(L), L >= 0 -> 1478 | set_env(stdlib, shell_history_length, L, ?DEF_HISTORY). 1479 | 1480 | -spec results(N) -> non_neg_integer() when 1481 | N :: non_neg_integer(). 1482 | 1483 | results(L) when is_integer(L), L >= 0 -> 1484 | set_env(stdlib, shell_saved_results, L, ?DEF_RESULTS). 1485 | 1486 | -spec catch_exception(Bool) -> boolean() when 1487 | Bool :: boolean(). 1488 | 1489 | catch_exception(Bool) -> 1490 | set_env(stdlib, shell_catch_exception, Bool, ?DEF_CATCH_EXCEPTION). 1491 | 1492 | -spec prompt_func(PromptFunc) -> PromptFunc2 when 1493 | PromptFunc :: 'default' | {module(),atom()}, 1494 | PromptFunc2 :: 'default' | {module(),atom()}. 1495 | 1496 | prompt_func(String) -> 1497 | set_env(stdlib, shell_prompt_func, String, ?DEF_PROMPT_FUNC). 1498 | 1499 | -spec strings(Strings) -> Strings2 when 1500 | Strings :: boolean(), 1501 | Strings2 :: boolean(). 1502 | 1503 | strings(Strings) -> 1504 | set_env(stdlib, shell_strings, Strings, ?DEF_STRINGS). 1505 | -------------------------------------------------------------------------------- /src/3.4/shell_profile.erl: -------------------------------------------------------------------------------- 1 | %%%------------------------------------------------------------------- 2 | %%% 3 | %%% @author Prakash Parmar 4 | %%% 5 | %%% @version Beta 6 | %%% 7 | %%% @doc 8 | %%% Module Contains Default profile settings and API to beautify prints. 9 | %%% only ANSI Color codes are supprots. 10 | %%% 11 | %%% ANSI Color Codes Guide : 12 | %%% ======================== 13 | %%% 14 | %%% \e[0;33;0m -> Here first Digit indicates Text attributes, 15 | %%% Second Digit indicates Foregound Color 16 | %%% and Last Third Digit indicates Background Color 17 | %%% 18 | %%% So, \e['Text_attribute';'Foreground_color';'Background_color'm 19 | %%% 20 | %%% Possible values : 21 | %%% ------------------ 22 | %%% Text Attributes- 0 default text attributes 23 | %%% 1 Bright 24 | %%% 2 Dim 25 | %%% 4 underline 26 | %%% 7 inverse 27 | %%% 8 Hidde 28 | %%% 29 | %%% Foreground Color - 30 Black 30 | %%% 31 Red 31 | %%% 32 Green 32 | %%% 33 Yellow 33 | %%% 34 Blue 34 | %%% 35 Magenta 35 | %%% 36 Cyan 36 | %%% 37 White 37 | %%% 39 default foreground 38 | %%% 39 | %%% Background Color - 40 Black 40 | %%% 41 Red 41 | %%% 42 Green 42 | %%% 43 Yellow 43 | %%% 44 Blue 44 | %%% 45 Magenta 45 | %%% 46 Cyan 46 | %%% 47 White 47 | %%% 49 default foreground 48 | %%% 49 | %%% @end 50 | %%% 51 | %%% @copyright 2017 Prakash Parmar 52 | %%%------------------------------------------------------------------- 53 | 54 | -module(shell_profile). 55 | 56 | %% ==================================================================== 57 | %% API functions 58 | %% ==================================================================== 59 | -export([ 60 | beautify/2 61 | ]). 62 | 63 | %% ==================================================================== 64 | %% Definitions 65 | %% ==================================================================== 66 | 67 | -define( DEFAULT_PROFILE, [ 68 | { sb, "\e[0;33m~ts\e[0m" }, % $[] % NOTE : if Changing Color code than 69 | % also Modify lib.erl line:661 70 | 71 | { cb, "\e[1;34m~ts\e[0m" }, % ${} 72 | 73 | { rb, "\e[1;34m~ts\e[0m" }, % $() 74 | { bstr, "\e[0;34m~ts\e[0m" }, % $<< >> 75 | 76 | { pipe, "\e[0;34m~ts\e[0m" }, % $| 77 | { comma, "\e[1;36m~ts\e[0m" }, % $, 78 | { colon, "\e[0;34m~ts\e[0m" }, % $: 79 | { hash, "\e[1;34m~ts\e[0m" }, % $# 80 | { ellipsis, "\e[0;34m~ts\e[0m" }, % $... 81 | { eq, "\e[0;34m~ts\e[0m" }, % = 82 | { arrow, "\e[0;34m~ts\e[0m" }, % => 83 | 84 | { term, "\e[0;32m~ts\e[0m" }, % atom, integer 85 | { name, "\e[1;35m~ts\e[0m" }, % record and map name 86 | { field, "\e[0;34m~ts\e[0m" }, % record field 87 | { string, "\e[0;33m~ts\e[0m" }, 88 | { digits, "\e[0;32m~ts\e[0m" }, 89 | { function, "\e[0;35m~ts\e[0m" }, % function name 90 | { prompt, "\e[2;33m~ts\e[0m" } % Shell prompt 91 | 92 | ]). 93 | 94 | %% ==================================================================== 95 | %% APIs 96 | %% ==================================================================== 97 | 98 | %%------------------------------------------------------------------- 99 | %% @doc 100 | %% Function Based on Class and pre-defined Color Schems adds 101 | %% Color codes to input string. 102 | %% 103 | %% Function Arguments : 104 | %% Argument 1 : An Atom indicates string type 105 | %% Possible Values : sb, cb, rb, bstr, pipe, comma,colon, 106 | %% hash, ellipsis, eq, arrow, term, name, 107 | %% field, string, digits, function, prompt 108 | %% 109 | %% Argument 2 : A String, 110 | %% For Eg, "[" or "atom"' 111 | %% @spec beautify( atom(), list() ) -> list(). 112 | %%------------------------------------------------------------------- 113 | 114 | beautify(Class,Str) -> 115 | case lists:keyfind(Class, 1, ?DEFAULT_PROFILE) of 116 | { Class, Format_specifier } -> 117 | lists:flatten(io_lib:format(Format_specifier,[Str])); 118 | _not_found -> 119 | Str 120 | end. 121 | --------------------------------------------------------------------------------