-module(textmetrics@edit).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/textmetrics/edit.gleam").
-export([recover_old/1, recover_new/1, cost/1, runs/1]).
-export_type([edit/1, run/1]).
-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(
" Edit-script algebraic data type and helpers shared by the diff\n"
" module.\n"
"\n"
" An [`EditScript`](#EditScript) is a linear sequence of\n"
" [`Edit`](#Edit) values describing how to transform one list into\n"
" another. Replaying the `Equal` and `Delete` steps recovers the\n"
" original (`old`) list, while replaying `Equal` and `Insert` steps\n"
" recovers the new list.\n"
"\n"
" This module operates on plain lists; it does not perform Unicode\n"
" normalization. Callers comparing strings should split inputs into\n"
" graphemes via `gleam/string.to_graphemes` before computing a diff.\n"
).
-type edit(DKT) :: {equal, DKT} | {insert, DKT} | {delete, DKT}.
-type run(DKU) :: {equal_run, list(DKU)} |
{insert_run, list(DKU)} |
{delete_run, list(DKU)}.
-file("src/textmetrics/edit.gleam", 54).
-spec do_recover_old(list(edit(DLB)), list(DLB)) -> list(DLB).
do_recover_old(Script, Acc) ->
case Script of
[] ->
lists:reverse(Acc);
[{equal, Item} | Rest] ->
do_recover_old(Rest, [Item | Acc]);
[{delete, Item@1} | Rest@1] ->
do_recover_old(Rest@1, [Item@1 | Acc]);
[{insert, _} | Rest@2] ->
do_recover_old(Rest@2, Acc)
end.
-file("src/textmetrics/edit.gleam", 50).
?DOC(
" Replay the `Equal` and `Delete` steps of `script` to recover the\n"
" `old` list.\n"
"\n"
" `recover_old(diff(old, new)) == old` for every input pair, by\n"
" construction.\n"
).
-spec recover_old(list(edit(DKY))) -> list(DKY).
recover_old(Script) ->
do_recover_old(Script, []).
-file("src/textmetrics/edit.gleam", 72).
-spec do_recover_new(list(edit(DLI)), list(DLI)) -> list(DLI).
do_recover_new(Script, Acc) ->
case Script of
[] ->
lists:reverse(Acc);
[{equal, Item} | Rest] ->
do_recover_new(Rest, [Item | Acc]);
[{insert, Item@1} | Rest@1] ->
do_recover_new(Rest@1, [Item@1 | Acc]);
[{delete, _} | Rest@2] ->
do_recover_new(Rest@2, Acc)
end.
-file("src/textmetrics/edit.gleam", 68).
?DOC(
" Replay the `Equal` and `Insert` steps of `script` to recover the\n"
" `new` list.\n"
"\n"
" `recover_new(diff(old, new)) == new` for every input pair, by\n"
" construction.\n"
).
-spec recover_new(list(edit(DLF))) -> list(DLF).
recover_new(Script) ->
do_recover_new(Script, []).
-file("src/textmetrics/edit.gleam", 90).
-spec do_cost(list(edit(any())), integer()) -> integer().
do_cost(Script, Acc) ->
case Script of
[] ->
Acc;
[{equal, _} | Rest] ->
do_cost(Rest, Acc);
[{insert, _} | Rest@1] ->
do_cost(Rest@1, Acc + 1);
[{delete, _} | Rest@2] ->
do_cost(Rest@2, Acc + 1)
end.
-file("src/textmetrics/edit.gleam", 86).
?DOC(
" Total number of `Insert` and `Delete` operations in `script`.\n"
"\n"
" For an optimal Myers diff this equals\n"
" `distance.levenshtein_list(old, new)` with substitution counted as\n"
" one insert plus one delete.\n"
).
-spec cost(list(edit(any()))) -> integer().
cost(Script) ->
do_cost(Script, 0).
-file("src/textmetrics/edit.gleam", 122).
-spec take_while_equal(list(edit(DMA)), list(DMA)) -> {list(DMA),
list(edit(DMA))}.
take_while_equal(Script, Acc) ->
case Script of
[{equal, Item} | Rest] ->
take_while_equal(Rest, [Item | Acc]);
_ ->
{lists:reverse(Acc), Script}
end.
-file("src/textmetrics/edit.gleam", 132).
-spec take_while_insert(list(edit(DMF)), list(DMF)) -> {list(DMF),
list(edit(DMF))}.
take_while_insert(Script, Acc) ->
case Script of
[{insert, Item} | Rest] ->
take_while_insert(Rest, [Item | Acc]);
_ ->
{lists:reverse(Acc), Script}
end.
-file("src/textmetrics/edit.gleam", 142).
-spec take_while_delete(list(edit(DMK)), list(DMK)) -> {list(DMK),
list(edit(DMK))}.
take_while_delete(Script, Acc) ->
case Script of
[{delete, Item} | Rest] ->
take_while_delete(Rest, [Item | Acc]);
_ ->
{lists:reverse(Acc), Script}
end.
-file("src/textmetrics/edit.gleam", 104).
-spec do_runs(list(edit(DLU)), list(run(DLU))) -> list(run(DLU)).
do_runs(Script, Acc) ->
case Script of
[] ->
lists:reverse(Acc);
[{equal, Item} | Rest] ->
{Items, Remaining} = take_while_equal(Rest, [Item]),
do_runs(Remaining, [{equal_run, Items} | Acc]);
[{insert, Item@1} | Rest@1] ->
{Items@1, Remaining@1} = take_while_insert(Rest@1, [Item@1]),
do_runs(Remaining@1, [{insert_run, Items@1} | Acc]);
[{delete, Item@2} | Rest@2] ->
{Items@2, Remaining@2} = take_while_delete(Rest@2, [Item@2]),
do_runs(Remaining@2, [{delete_run, Items@2} | Acc])
end.
-file("src/textmetrics/edit.gleam", 100).
?DOC(" Group consecutive edits that share a constructor into runs.\n").
-spec runs(list(edit(DLQ))) -> list(run(DLQ)).
runs(Script) ->
do_runs(Script, []).