Fork of daniellemaywood.uk/gleam — Wasm codegen work
7.2 kB
210 lines
1-define(is_lowercase_char(X),
2 (X > 96 andalso X < 123)).
3
4-define(is_underscore_char(X),
5 (X == 95)).
6
7-define(is_digit_char(X),
8 (X > 47 andalso X < 58)).
9
10-define(is_ascii_character(X),
11 (erlang:is_integer(X) andalso X >= 32 andalso X =< 126)).
12
13-define(could_be_record(Tuple),
14 erlang:is_tuple(Tuple) andalso
15 erlang:is_atom(erlang:element(1, Tuple)) andalso
16 erlang:element(1, Tuple) =/= false andalso
17 erlang:element(1, Tuple) =/= true andalso
18 erlang:element(1, Tuple) =/= nil
19).
20-define(is_atom_char(C),
21 (?is_lowercase_char(C) orelse
22 ?is_underscore_char(C) orelse
23 ?is_digit_char(C))
24).
25
26-define(grey, "\e[90m").
27-define(reset_color, "\e[39m").
28
29echo(Value, Message, Filepath, Line) ->
30 StringLine = erlang:integer_to_list(Line),
31 StringValue = echo@inspect(Value),
32 StringMessage =
33 case Message of
34 nil -> "";
35 M -> [" ", M]
36 end,
37
38 io:put_chars(
39 standard_error,
40 [
41 ?grey, Filepath, $:, StringLine, ?reset_color, StringMessage, $\n,
42 StringValue, $\n
43 ]
44 ),
45 Value.
46
47echo@inspect(Value) ->
48 case Value of
49 nil -> "Nil";
50 true -> "True";
51 false -> "False";
52 Int when erlang:is_integer(Int) -> erlang:integer_to_list(Int);
53 Float when erlang:is_float(Float) -> io_lib_format:fwrite_g(Float);
54 Binary when erlang:is_binary(Binary) -> inspect@binary(Binary);
55 Bits when erlang:is_bitstring(Bits) -> inspect@bit_array(Bits);
56 Atom when erlang:is_atom(Atom) -> inspect@atom(Atom);
57 List when erlang:is_list(List) -> inspect@list(List);
58 Map when erlang:is_map(Map) -> inspect@map(Map);
59 Record when ?could_be_record(Record) -> inspect@record(Record);
60 Tuple when erlang:is_tuple(Tuple) -> inspect@tuple(Tuple);
61 Function when erlang:is_function(Function) -> inspect@function(Function);
62 Any -> ["//erl(", io_lib:format("~p", [Any]), ")"]
63 end.
64
65inspect@bit_array(Bits) ->
66 Pieces = inspect@bit_array_pieces(Bits, []),
67 Inner = lists:join(", ", lists:reverse(Pieces)),
68 ["<<", Inner, ">>"].
69
70inspect@bit_array_pieces(Bits, Acc) ->
71 case Bits of
72 <<>> ->
73 Acc;
74 <<Byte, Rest/bitstring>> ->
75 inspect@bit_array_pieces(Rest, [erlang:integer_to_binary(Byte) | Acc]);
76 _ ->
77 Size = erlang:bit_size(Bits),
78 <<RemainingBits:Size>> = Bits,
79 SizeString = [":size(", erlang:integer_to_binary(Size), ")"],
80 Piece = [erlang:integer_to_binary(RemainingBits), SizeString],
81 [Piece | Acc]
82 end.
83
84inspect@binary(Binary) ->
85 case inspect@maybe_utf8_string(Binary, <<>>) of
86 {ok, InspectedUtf8String} ->
87 InspectedUtf8String;
88 {error, not_a_utf8_string} ->
89 Segments = [erlang:integer_to_list(X) || <<X>> <= Binary],
90 ["<<", lists:join(", ", Segments), ">>"]
91 end.
92
93inspect@atom(Atom) ->
94 Binary = erlang:atom_to_binary(Atom),
95 case inspect@maybe_gleam_atom(Binary, none, <<>>) of
96 {ok, Inspected} -> Inspected;
97 {error, _} -> ["atom.create(\"", Binary, "\")"]
98 end.
99
100inspect@list(List) ->
101 case inspect@list_loop(List, true) of
102 {charlist, _} -> ["charlist.from_string(\"", erlang:list_to_binary(List), "\")"];
103 {proper, Elements} -> ["[", Elements, "]"];
104 {improper, Elements} -> ["//erl([", Elements, "])"]
105 end.
106
107inspect@map(Map) ->
108 Fields = [
109 [<<"#(">>, echo@inspect(Key), <<", ">>, echo@inspect(Value), <<")">>]
110 || {Key, Value} <- maps:to_list(Map)
111 ],
112 ["dict.from_list([", lists:join(", ", Fields), "])"].
113
114inspect@record(Record) ->
115 [Atom | ArgsList] = Tuple = erlang:tuple_to_list(Record),
116 case inspect@maybe_gleam_atom(Atom, none, <<>>) of
117 {ok, Tag} ->
118 Args = lists:join(", ", lists:map(fun echo@inspect/1, ArgsList)),
119 [Tag, "(", Args, ")"];
120 _ ->
121 inspect@tuple(Tuple)
122 end.
123
124inspect@tuple(Tuple) when erlang:is_tuple(Tuple) ->
125 inspect@tuple(erlang:tuple_to_list(Tuple));
126inspect@tuple(Tuple) ->
127 Elements = lists:map(fun echo@inspect/1, Tuple),
128 ["#(", lists:join(", ", Elements), ")"].
129
130inspect@function(Function) ->
131 {arity, Arity} = erlang:fun_info(Function, arity),
132 ArgsAsciiCodes = lists:seq($a, $a + Arity - 1),
133 Args = lists:join(", ", lists:map(fun(Arg) -> <<Arg>> end, ArgsAsciiCodes)),
134 ["//fn(", Args, ") { ... }"].
135
136inspect@maybe_utf8_string(Binary, Acc) ->
137 case Binary of
138 <<>> ->
139 {ok, <<$", Acc/binary, $">>};
140 <<First/utf8, Rest/binary>> ->
141 Escaped = inspect@escape_grapheme(First),
142 inspect@maybe_utf8_string(Rest, <<Acc/binary, Escaped/binary>>);
143 _ ->
144 {error, not_a_utf8_string}
145 end.
146
147inspect@escape_grapheme(Char) ->
148 case Char of
149 $" -> <<$\\, $">>;
150 $\\ -> <<$\\, $\\>>;
151 $\r -> <<$\\, $r>>;
152 $\n -> <<$\\, $n>>;
153 $\t -> <<$\\, $t>>;
154 $\f -> <<$\\, $f>>;
155 X when X > 126, X < 160 -> inspect@convert_to_u(X);
156 X when X < 32 -> inspect@convert_to_u(X);
157 Other -> <<Other/utf8>>
158 end.
159
160inspect@convert_to_u(Code) ->
161 erlang:list_to_binary(io_lib:format("\\u{~4.16.0B}", [Code])).
162
163inspect@list_loop(List, Ascii) ->
164 case List of
165 [] ->
166 {proper, []};
167 [First] when Ascii andalso ?is_ascii_character(First) ->
168 {charlist, nil};
169 [First] ->
170 {proper, [echo@inspect(First)]};
171 [First | Rest] when erlang:is_list(Rest) ->
172 StillAscii = Ascii andalso ?is_ascii_character(First),
173 {Kind, Inspected} = inspect@list_loop(Rest, StillAscii),
174 {Kind, [echo@inspect(First), ", " | Inspected]};
175 [First | ImproperRest] ->
176 {improper, [echo@inspect(First), " | ", echo@inspect(ImproperRest)]}
177 end.
178
179inspect@maybe_gleam_atom(Atom, PrevChar, Acc) when erlang:is_atom(Atom) ->
180 Binary = erlang:atom_to_binary(Atom),
181 inspect@maybe_gleam_atom(Binary, PrevChar, Acc);
182inspect@maybe_gleam_atom(Atom, PrevChar, Acc) ->
183 case {Atom, PrevChar} of
184 {<<>>, none} ->
185 {error, nil};
186 {<<First, _/binary>>, none} when ?is_digit_char(First) ->
187 {error, nil};
188 {<<"_", _/binary>>, none} ->
189 {error, nil};
190 {<<"_">>, _} ->
191 {error, nil};
192 {<<"_", _/binary>>, $_} ->
193 {error, nil};
194 {<<First, _/binary>>, _} when not ?is_atom_char(First) ->
195 {error, nil};
196 {<<First, Rest/binary>>, none} ->
197 inspect@maybe_gleam_atom(Rest, First, <<Acc/binary, (inspect@uppercase(First))>>);
198 {<<"_", Rest/binary>>, _} ->
199 inspect@maybe_gleam_atom(Rest, $_, Acc);
200 {<<First, Rest/binary>>, $_} ->
201 inspect@maybe_gleam_atom(Rest, First, <<Acc/binary, (inspect@uppercase(First))>>);
202 {<<First, Rest/binary>>, _} ->
203 inspect@maybe_gleam_atom(Rest, First, <<Acc/binary, First>>);
204 {<<>>, _} ->
205 {ok, Acc};
206 _ ->
207 erlang:throw({gleam_error, echo, Atom, PrevChar, Acc})
208 end.
209
210inspect@uppercase(X) -> X - 32.