Current section
Files
Jump to
Current section
Files
lib/ot/text/composition.ex
defmodule OT.Text.Composition do
@moduledoc """
The composition of two non-concurrent operations into a single operation.
"""
alias OT.Text.{Operation, Scanner}
@doc """
Compose two operations into a single equivalent operation.
The operations are composed in such a way that the resulting operation has the
same effect on document state as applying one operation and then the other:
*S ○ compose(Oa, Ob) = S ○ Oa ○ Ob*.
## Example
iex> OT.Text.Composition.compose([%{i: "Bar"}], [%{i: "Foo"}])
[%{i: "FooBar"}]
"""
@spec compose(Operation.t, Operation.t) :: Operation.t
def compose(op_a, op_b) do
{op_a, op_b}
|> next
|> do_compose
end
@spec do_compose(Scanner.output, Operation.t) :: Operation.t
defp do_compose(next_pair, result \\ [])
# Both operations are exhausted.
defp do_compose({{nil, _}, {nil, _}}, result),
do: result
# A is exhausted.
defp do_compose({{nil, _}, {head_b, tail_b}}, result) do
result
|> Operation.append(head_b)
|> Operation.join(tail_b)
end
# B is exhausted.
defp do_compose({{head_a, tail_a}, {nil, _}}, result) do
result
|> Operation.append(head_a)
|> Operation.join(tail_a)
end
# _ / insert
defp do_compose({{head_a, tail_a},
{head_b = %{i: _}, tail_b}}, result) do
{[head_a | tail_a], tail_b}
|> next
|> do_compose(Operation.append(result, head_b))
end
# insert / retain
defp do_compose({{head_a = %{i: _}, tail_a},
{retain_b, tail_b}}, result) when is_integer(retain_b) do
{tail_a, tail_b}
|> next
|> do_compose(Operation.append(result, head_a))
end
# insert / delete
defp do_compose({{%{i: _}, tail_a}, {%{d: _}, tail_b}}, result) do
{tail_a, tail_b}
|> next
|> do_compose(result)
end
# retain / retain
defp do_compose({{retain_a, tail_a}, {retain_b, tail_b}}, result)
when is_integer(retain_a) and is_integer(retain_b) do
{tail_a, tail_b}
|> next
|> do_compose(Operation.append(result, retain_a))
end
# retain / delete
defp do_compose({{retain_a, tail_a},
{head_b = %{d: _}, tail_b}}, result)
when is_integer(retain_a) do
{tail_a, tail_b}
|> next
|> do_compose(Operation.append(result, head_b))
end
# delete / retain
defp do_compose({{head_a = %{d: _}, tail_a},
{retain_b, tail_b}}, result)
when is_integer(retain_b) do
{tail_a, [retain_b | tail_b]}
|> next
|> do_compose(Operation.append(result, head_a))
end
# delete / delete
defp do_compose({{head_a = %{d: _}, tail_a},
{head_b = %{d: _}, tail_b}}, result) do
{tail_a, [head_b | tail_b]}
|> next
|> do_compose(Operation.append(result, head_a))
end
@spec next(Scanner.input) :: Scanner.output
defp next(scanner_input), do: Scanner.next(scanner_input, :delete)
end