-module(magic_string@codec).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/magic_string/codec.gleam").
-export([encode_vlq/1, decode_vlq/1, decode_mappings/1, generate_mappings/1, to_json/1, url_comment/2]).
-export_type([source_map/0, map_mode/0, segment/0, delta_state/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Source Map v3 model and base64 VLQ codec.\n"
"\n"
" `generate_mappings` turns a flat `Segment` list into the v3 `mappings`\n"
" string; `decode_mappings` is its inverse. `to_json` serializes the whole\n"
" `SourceMap`. See https://tc39.es/ecma426/ for the format.\n"
).
-type source_map() :: {source_map,
integer(),
gleam@option:option(binary()),
gleam@option:option(binary()),
list(binary()),
list(gleam@option:option(binary())),
list(binary()),
binary()}.
-type map_mode() :: {external, binary()} | inline | hidden.
-type segment() :: {segment,
integer(),
integer(),
integer(),
integer(),
integer()}.
-type delta_state() :: {delta_state, integer(), integer(), integer()}.
-file("src/magic_string/codec.gleam", 84).
-spec base64_char(integer()) -> binary().
base64_char(N) ->
gleam@string:slice(
<<"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"/utf8>>,
N,
1
).
-file("src/magic_string/codec.gleam", 74).
-spec encode_vlq_loop(integer(), binary()) -> binary().
encode_vlq_loop(Vlq, Acc) ->
Digit = erlang:'band'(Vlq, 31),
Rest = erlang:'bsr'(Vlq, 5),
case Rest > 0 of
true ->
encode_vlq_loop(
Rest,
<<Acc/binary, (base64_char(erlang:'bor'(Digit, 32)))/binary>>
);
false ->
<<Acc/binary, (base64_char(Digit))/binary>>
end.
-file("src/magic_string/codec.gleam", 66).
?DOC(
" Encode a single signed integer as a base64 VLQ string.\n"
"\n"
" Sign is folded into the low bit (`value << 1`, or `(-value << 1) | 1` for\n"
" negatives), then the magnitude is split into 5-bit groups emitted\n"
" low-group-first. Every group except the last carries the continuation bit\n"
" (32); each resulting 6-bit value indexes into `base64_alphabet`.\n"
).
-spec encode_vlq(integer()) -> binary().
encode_vlq(Value) ->
Vlq = case Value < 0 of
true ->
erlang:'bor'(erlang:'bsl'(- Value, 1), 1);
false ->
erlang:'bsl'(Value, 1)
end,
encode_vlq_loop(Vlq, <<""/utf8>>).
-file("src/magic_string/codec.gleam", 132).
?DOC(" Undo the sign-in-low-bit folding `encode_vlq` applies.\n").
-spec from_vlq(integer()) -> integer().
from_vlq(Value) ->
Magnitude = erlang:'bsr'(Value, 1),
case erlang:'band'(Value, 1) of
0 ->
Magnitude;
_ ->
- Magnitude
end.
-file("src/magic_string/codec.gleam", 159).
-spec char_code(binary()) -> integer().
char_code(Char) ->
_pipe = Char,
_pipe@1 = gleam@string:to_utf_codepoints(_pipe),
_pipe@2 = gleam@list:first(_pipe@1),
_pipe@3 = gleam@result:map(_pipe@2, fun gleam_stdlib:identity/1),
gleam@result:unwrap(_pipe@3, 0).
-file("src/magic_string/codec.gleam", 141).
?DOC(" Position of a base64 character in the Source Map v3 alphabet.\n").
-spec base64_value(binary()) -> {ok, integer()} | {error, binary()}.
base64_value(Char) ->
case char_code(Char) of
Code when (Code >= 65) andalso (Code =< 90) ->
{ok, Code - 65};
Code@1 when (Code@1 >= 97) andalso (Code@1 =< 122) ->
{ok, (Code@1 - 97) + 26};
Code@2 when (Code@2 >= 48) andalso (Code@2 =< 57) ->
{ok, (Code@2 - 48) + 52};
43 ->
{ok, 62};
47 ->
{ok, 63};
Code@3 ->
{error,
<<<<<<<<"invalid base64 VLQ character '"/utf8, Char/binary>>/binary,
"' (codepoint "/utf8>>/binary,
(erlang:integer_to_binary(Code@3))/binary>>/binary,
")"/utf8>>}
end.
-file("src/magic_string/codec.gleam", 98).
-spec decode_vlq_loop(list(binary()), integer(), integer(), list(integer())) -> {ok,
list(integer())} |
{error, binary()}.
decode_vlq_loop(Chars, Partial, Shift, Acc) ->
case Chars of
[] ->
case Shift of
0 ->
{ok, lists:reverse(Acc)};
_ ->
{error,
<<<<"truncated VLQ: continuation bit set on final digit (shift "/utf8,
(erlang:integer_to_binary(Shift))/binary>>/binary,
")"/utf8>>}
end;
[C | Rest] ->
case base64_value(C) of
{error, Msg} ->
{error, Msg};
{ok, Digit} ->
Chunk = erlang:'band'(Digit, 31),
Partial@1 = Partial + erlang:'bsl'(Chunk, Shift),
case erlang:'band'(Digit, 32) of
0 ->
decode_vlq_loop(
Rest,
0,
0,
[from_vlq(Partial@1) | Acc]
);
_ ->
decode_vlq_loop(Rest, Partial@1, Shift + 5, Acc)
end
end
end.
-file("src/magic_string/codec.gleam", 94).
?DOC(
" Decode a single base64-VLQ field string back to its list of signed\n"
" integers. This is the exact inverse of concatenating `encode_vlq` calls:\n"
" `decode_vlq(encode_vlq(a) <> encode_vlq(b)) == [a, b]`.\n"
"\n"
" Returns `Error` when `field` contains a character outside the base64\n"
" alphabet, or ends mid-value (a continuation bit with no following digit).\n"
).
-spec decode_vlq(binary()) -> {ok, list(integer())} | {error, binary()}.
decode_vlq(Field) ->
decode_vlq_loop(gleam@string:to_graphemes(Field), 0, 0, []).
-file("src/magic_string/codec.gleam", 192).
-spec decode_line(binary(), integer(), {list(segment()), delta_state()}) -> {ok,
{list(segment()), delta_state()}} |
{error, binary()}.
decode_line(Line, Gen_line, Acc) ->
case Line of
<<""/utf8>> ->
{ok, Acc};
_ ->
_pipe = gleam@string:split(Line, <<","/utf8>>),
_pipe@2 = gleam@list:try_fold(
_pipe,
{erlang:element(1, Acc), erlang:element(2, Acc), 0},
fun(State, Field) ->
{Segs, Delta, Prev_gen_col} = State,
gleam@result:'try'(
begin
_pipe@1 = decode_vlq(Field),
gleam@result:map_error(
_pipe@1,
fun(Err) ->
<<<<<<"line "/utf8,
(erlang:integer_to_binary(
Gen_line
))/binary>>/binary,
": "/utf8>>/binary,
Err/binary>>
end
)
end,
fun(Values) -> case Values of
[Gc, Si, Ol, Oc | _] ->
Gen_col = Prev_gen_col + Gc,
Source_idx = erlang:element(2, Delta) + Si,
Orig_line = erlang:element(3, Delta) + Ol,
Orig_col = erlang:element(4, Delta) + Oc,
Seg = {segment,
Gen_line,
Gen_col,
Source_idx,
Orig_line,
Orig_col},
{ok,
{[Seg | Segs],
{delta_state,
Source_idx,
Orig_line,
Orig_col},
Gen_col}};
[Gc@1] ->
{ok, {Segs, Delta, Prev_gen_col + Gc@1}};
Other ->
{error,
<<<<<<<<"line "/utf8,
(erlang:integer_to_binary(
Gen_line
))/binary>>/binary,
": segment has "/utf8>>/binary,
(erlang:integer_to_binary(
erlang:length(Other)
))/binary>>/binary,
" fields; expected 1, 4, or 5"/utf8>>}
end end
)
end
),
gleam@result:map(
_pipe@2,
fun(State@1) ->
{erlang:element(1, State@1), erlang:element(2, State@1)}
end
)
end.
-file("src/magic_string/codec.gleam", 177).
?DOC(
" Decode a Source Map v3 `mappings` string back to a flat `Segment` list.\n"
" This is the inverse of `generate_mappings`:\n"
" `decode_mappings(generate_mappings(segs))` returns the same segments\n"
" (in `(gen_line, gen_col)` order).\n"
"\n"
" Only 4- and 5-field segments (those carrying a source pointer) are\n"
" returned; 1-field segments (generated column only, no source) are skipped\n"
" since `Segment` requires a source position. The optional 5th `name` field\n"
" is decoded for delta-tracking but discarded (this codec does not model\n"
" names).\n"
).
-spec decode_mappings(binary()) -> {ok, list(segment())} | {error, binary()}.
decode_mappings(Mappings) ->
Lines = gleam@string:split(Mappings, <<";"/utf8>>),
gleam@result:map(
gleam@list:try_fold(
gleam@list:index_map(Lines, fun(Line, Idx) -> {Idx, Line} end),
{[], {delta_state, 0, 0, 0}},
fun(Acc, Entry) ->
{Gen_line, Line@1} = Entry,
decode_line(Line@1, Gen_line, Acc)
end
),
fun(_use0) ->
{Segs_rev, _} = _use0,
lists:reverse(Segs_rev)
end
).
-file("src/magic_string/codec.gleam", 288).
?DOC(
" Encode one output line's segments, threading the cross-map delta state in\n"
" and out. `gen_col` resets to 0 at the start of every line.\n"
).
-spec emit_line(list(segment()), delta_state()) -> {binary(), delta_state()}.
emit_line(Segs, Delta) ->
{Parts_rev, _, Next_delta} = gleam@list:fold(
Segs,
{[], 0, Delta},
fun(Acc, Seg) ->
{Parts, Prev_gen_col, D} = Acc,
Field = erlang:list_to_binary(
[encode_vlq(erlang:element(3, Seg) - Prev_gen_col),
encode_vlq(erlang:element(4, Seg) - erlang:element(2, D)),
encode_vlq(erlang:element(5, Seg) - erlang:element(3, D)),
encode_vlq(erlang:element(6, Seg) - erlang:element(4, D))]
),
Next = {delta_state,
erlang:element(4, Seg),
erlang:element(5, Seg),
erlang:element(6, Seg)},
{[Field | Parts], erlang:element(3, Seg), Next}
end
),
{begin
_pipe = Parts_rev,
_pipe@1 = lists:reverse(_pipe),
gleam@string:join(_pipe@1, <<","/utf8>>)
end,
Next_delta}.
-file("src/magic_string/codec.gleam", 305).
-spec compare_segment(segment(), segment()) -> gleam@order:order().
compare_segment(A, B) ->
case gleam@int:compare(erlang:element(2, A), erlang:element(2, B)) of
eq ->
gleam@int:compare(erlang:element(3, A), erlang:element(3, B));
Other ->
Other
end.
-file("src/magic_string/codec.gleam", 254).
?DOC(
" Build the Source Map v3 `mappings` string from a flat segment list.\n"
"\n"
" Output lines are `;`-separated and segments within a line are\n"
" `,`-separated. Each segment is the VLQ of four deltas:\n"
" `[gen_col, source_idx, orig_line, orig_col]`. `gen_col` is delta'd within\n"
" the line (reset to 0 per line); the other three are delta'd across the\n"
" whole map. Inserted text contributes no segment (so a line with only\n"
" inserted output is empty). `hires` is false: one segment per chunk.\n"
).
-spec generate_mappings(list(segment())) -> binary().
generate_mappings(Segments) ->
case Segments of
[] ->
<<""/utf8>>;
_ ->
Sorted = gleam@list:sort(Segments, fun compare_segment/2),
Max_line = gleam@list:fold(
Sorted,
0,
fun(Acc, S) -> case erlang:element(2, S) > Acc of
true ->
erlang:element(2, S);
false ->
Acc
end end
),
By_line = gleam@list:group(
Sorted,
fun(S@1) -> erlang:element(2, S@1) end
),
{Lines_rev, _} = gleam@int:range(
0,
Max_line + 1,
{[], {delta_state, 0, 0, 0}},
fun(Acc@1, Line) ->
{Lines, Delta} = Acc@1,
Segs = begin
_pipe = gleam_stdlib:map_get(By_line, Line),
_pipe@1 = gleam@result:unwrap(_pipe, []),
gleam@list:sort(
_pipe@1,
fun(A, B) ->
gleam@int:compare(
erlang:element(3, A),
erlang:element(3, B)
)
end
)
end,
{Line_str, Next_delta} = emit_line(Segs, Delta),
{[Line_str | Lines], Next_delta}
end
),
_pipe@2 = Lines_rev,
_pipe@3 = lists:reverse(_pipe@2),
gleam@string:join(_pipe@3, <<";"/utf8>>)
end.
-file("src/magic_string/codec.gleam", 343).
-spec json_string(binary()) -> binary().
json_string(Value) ->
Escaped = begin
_pipe = Value,
_pipe@1 = gleam@string:replace(_pipe, <<"\\"/utf8>>, <<"\\\\"/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<"\""/utf8>>, <<"\\\""/utf8>>),
_pipe@3 = gleam@string:replace(_pipe@2, <<"\n"/utf8>>, <<"\\n"/utf8>>),
_pipe@4 = gleam@string:replace(_pipe@3, <<"\r"/utf8>>, <<"\\r"/utf8>>),
gleam@string:replace(_pipe@4, <<"\t"/utf8>>, <<"\\t"/utf8>>)
end,
<<<<"\""/utf8, Escaped/binary>>/binary, "\""/utf8>>.
-file("src/magic_string/codec.gleam", 332).
-spec json_array(list(binary())) -> binary().
json_array(Items) ->
<<<<"["/utf8, (gleam@string:join(Items, <<","/utf8>>))/binary>>/binary,
"]"/utf8>>.
-file("src/magic_string/codec.gleam", 336).
-spec json_nullable(gleam@option:option(binary())) -> binary().
json_nullable(Value) ->
case Value of
{some, S} ->
json_string(S);
none ->
<<"null"/utf8>>
end.
-file("src/magic_string/codec.gleam", 314).
?DOC(
" Serialize a `SourceMap` to a JSON string. `file` and `sourceRoot` are\n"
" omitted when `None`; `sourcesContent` entries are `null` when `None`.\n"
).
-spec to_json(source_map()) -> binary().
to_json(Map) ->
Fields = begin
_pipe = [{some,
<<"\"version\":"/utf8,
(erlang:integer_to_binary(erlang:element(2, Map)))/binary>>},
gleam@option:map(
erlang:element(3, Map),
fun(F) -> <<"\"file\":"/utf8, (json_string(F))/binary>> end
),
gleam@option:map(
erlang:element(4, Map),
fun(R) ->
<<"\"sourceRoot\":"/utf8, (json_string(R))/binary>>
end
),
{some,
<<"\"sources\":"/utf8,
(json_array(
gleam@list:map(
erlang:element(5, Map),
fun json_string/1
)
))/binary>>},
{some,
<<"\"sourcesContent\":"/utf8,
(json_array(
gleam@list:map(
erlang:element(6, Map),
fun json_nullable/1
)
))/binary>>},
{some,
<<"\"names\":"/utf8,
(json_array(
gleam@list:map(
erlang:element(7, Map),
fun json_string/1
)
))/binary>>},
{some,
<<"\"mappings\":"/utf8,
(json_string(erlang:element(8, Map)))/binary>>}],
gleam@option:values(_pipe)
end,
<<<<"{"/utf8, (gleam@string:join(Fields, <<","/utf8>>))/binary>>/binary,
"}"/utf8>>.
-file("src/magic_string/codec.gleam", 358).
?DOC(
" Produce the `sourceMappingURL` comment for the given map and mode.\n"
"\n"
" `External` points at a sibling `.map` URL, `Inline` embeds the whole map\n"
" as a base64 `data:` URI, and `Hidden` emits nothing.\n"
).
-spec url_comment(source_map(), map_mode()) -> binary().
url_comment(Map, Mode) ->
case Mode of
{external, Url} ->
<<"//# sourceMappingURL="/utf8, Url/binary>>;
hidden ->
<<""/utf8>>;
inline ->
B64 = gleam_stdlib:base64_encode(
gleam_stdlib:identity(to_json(Map)),
true
),
<<"//# sourceMappingURL=data:application/json;charset=utf-8;base64,"/utf8,
B64/binary>>
end.