Skip to content

Commit

Permalink
Type Text WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
point committed May 20, 2024
1 parent 8ca66b1 commit 6c7c74e
Show file tree
Hide file tree
Showing 10 changed files with 1,192 additions and 296 deletions.
File renamed without changes.
7 changes: 7 additions & 0 deletions lib/y/content/format.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule Y.Content.Format do
alias __MODULE__
defstruct [:key, :value]

def new(k, v), do: %Format{key: k, value: v}
def to_map(%Format{key: key, value: value}), do: %{key => value}
end
36 changes: 36 additions & 0 deletions lib/y/doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,19 @@ defmodule Y.Doc do
GenServer.call(doc_name, {:get_map, map_name})
end

def get_text(transaction, text_name \\ UUID.uuid4())

def get_text(%Transaction{doc: doc} = transaction, text_name) do
case do_get_text(doc, text_name) do
{:ok, text, doc} -> {:ok, text, %{transaction | doc: doc}}
{:error, _} = err -> err
end
end

def get_text(doc_name, text_name) do
GenServer.call(doc_name, {:get_text, text_name})
end

def transact(doc_name, f, opts \\ []) do
GenServer.call(doc_name, {:transact, f, opts})
end
Expand Down Expand Up @@ -309,6 +322,13 @@ defmodule Y.Doc do
end
end

def handle_call({:get_text, name}, _, doc) do
case do_get_text(doc, name) do
{:ok, text, doc} -> {:reply, {:ok, text}, doc}
{:error, _} = err -> {:reply, err, doc}
end
end

def handle_call(
{:transact, f, opts},
_,
Expand Down Expand Up @@ -418,6 +438,22 @@ defmodule Y.Doc do
{:ok, map, %Doc{doc | share: Map.put_new(doc.share, name, map)}}
end

defp do_get_text(%Doc{share: share} = doc, name) when is_map_key(share, name) do
case share[name] do
%Unknown{} = u ->
map = Y.Type.Text.from_unknown(u)

Check warning on line 444 in lib/y/doc.ex

View workflow job for this annotation

GitHub Actions / Build and test

Y.Type.Text.from_unknown/1 is undefined or private
{:ok, map, %{doc | share: Map.replace(share, name, map)}}

_ ->
{:error, "Type with the name #{name} has already been added"}
end
end

defp do_get_text(%Doc{} = doc, name) do
text = Y.Type.Text.new(doc, name)
{:ok, text, %Doc{doc | share: Map.put_new(doc.share, name, text)}}
end

defp do_find_parent(type, child_item) do
if Type.impl_for(type) do
type
Expand Down
301 changes: 5 additions & 296 deletions lib/y/type/array/array_tree.ex
Original file line number Diff line number Diff line change
@@ -1,64 +1,24 @@
defmodule Y.Type.Array.ArrayTree do
alias __MODULE__
alias FingerTree.EmptyTree
# alias FingerTree.Protocols.Conjable
alias Y.Item
alias Y.ID

defstruct [:ft]
@type t() :: %ArrayTree{ft: FingerTree.t()}
defstruct [:ft]

defmodule Meter do
@enforce_keys [:highest_clocks, :highest_clocks_with_length, :len]
defstruct highest_clocks: %{}, highest_clocks_with_length: %{}, len: 0
end

use Y.Type.GeneralTree, mod: ArrayTree

def new do
%ArrayTree{ft: FingerTree.finger_tree(meter_object())}
end

def highest_clock_with_length(%ArrayTree{ft: tree}, nil) do
%Meter{highest_clocks_with_length: cl} = FingerTree.measure(tree)

cl
|> Map.values()
|> Enum.max(fn -> 0 end)
end

def highest_clock_with_length(%ArrayTree{ft: tree}, client_id) do
%Meter{highest_clocks_with_length: cl} = FingerTree.measure(tree)

case Map.fetch(cl, client_id) do
{:ok, clock_len} -> clock_len
_ -> 0
end
end

def highest_clock(%ArrayTree{ft: tree}, nil) do
%Meter{highest_clocks: c} = FingerTree.measure(tree)

c
|> Map.values()
|> Enum.max(fn -> 0 end)
end

def highest_clock(%ArrayTree{ft: tree}, client_id) do
%Meter{highest_clocks: c} = FingerTree.measure(tree)

case Map.fetch(c, client_id) do
{:ok, clock} -> clock
_ -> 0
end
end

def highest_clock_with_length_by_client_id(%ArrayTree{ft: tree}) do
%Meter{highest_clocks_with_length: cl} = FingerTree.measure(tree)
cl
end

def highest_clock_by_client_id(%ArrayTree{ft: tree}) do
%Meter{highest_clocks: c} = FingerTree.measure(tree)
c
def new(meter_object) do
%ArrayTree{ft: FingerTree.finger_tree(meter_object)}
end

def put(%ArrayTree{ft: %EmptyTree{} = tree} = array_tree, _index, %Item{} = item) do
Expand Down Expand Up @@ -149,257 +109,6 @@ defmodule Y.Type.Array.ArrayTree do
{:ok, %{array_tree | ft: new_tree}}
end

def cons!(%ArrayTree{ft: tree} = array_tree, value),
do: %{array_tree | ft: FingerTree.cons(tree, value)}

def conj!(%ArrayTree{ft: tree} = array_tree, value),
do: %{array_tree | ft: FingerTree.conj(tree, value)}

@spec empty?(t()) :: boolean()
def empty?(%ArrayTree{ft: tree}), do: FingerTree.empty?(tree)

def to_list(%ArrayTree{ft: tree}), do: FingerTree.to_list(tree)

def find(tree, id, default \\ nil)

def find(%ArrayTree{ft: %EmptyTree{}}, _id, default), do: default

def find(%ArrayTree{ft: tree}, id, default) do
{l, v, _} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
case Map.fetch(clocks, id.client) do
{:ok, c} -> c >= id.clock
_ -> false
end
end)

prev = FingerTree.last(l)

cond do
v.id.clock == id.clock ->
v

id.clock > v.id.clock && id.clock <= v.id.clock + Item.content_length(v) ->
v

prev && id.clock > prev.id.clock && id.clock <= prev.id.clock + Item.content_length(prev) ->
prev

:otherwise ->
default
end
end

def replace(%ArrayTree{ft: tree} = array_tree, item, with_items) when is_list(with_items) do
{l, v, r} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
Map.fetch!(clocks, item.id.client) >= item.id.clock
end)

if v == item do
tree =
with_items
|> Enum.flat_map(&Item.explode/1)
|> Enum.reduce(l, fn item, tree -> FingerTree.conj(tree, item) end)
|> FingerTree.append(r)

{:ok, %{array_tree | ft: tree}}
else
{:error, "Item not found"}
end
end

def transform(%ArrayTree{ft: tree} = array_tree, %Item{} = starting_item, acc \\ nil, fun)
when is_function(fun, 2) do
{l, v, r} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
case Map.fetch(clocks, starting_item.id.client) do
{:ok, c} -> c >= starting_item.id.clock
_ -> false
end
end)

if v == starting_item do
tree =
case do_transform(v, l, r, acc, fun) do
{left_tree, nil} ->
left_tree

{left_tree, right_tree} ->
FingerTree.append(left_tree, right_tree)
end

{:ok, %{array_tree | ft: tree}}
else
{:error, "Item not found"}
end
end

defp do_transform(nil, left_tree, right_tree, _acc, _fun), do: {left_tree, right_tree}

defp do_transform(_, left_tree, nil, _acc, _fun),
do: {left_tree, nil}

defp do_transform(%Item{} = item, left_tree, right_tree, acc, fun) do
case fun.(item, acc) do
{%Item{} = new_item, new_acc} ->
do_transform(
FingerTree.first(right_tree),
FingerTree.conj(left_tree, new_item),
FingerTree.rest(right_tree),
new_acc,
fun
)

%Item{} = new_item ->
do_transform(
FingerTree.first(right_tree),
FingerTree.conj(left_tree, new_item),
FingerTree.rest(right_tree),
acc,
fun
)

nil ->
{FingerTree.conj(left_tree, item), right_tree}
end
end

def between(%ArrayTree{ft: tree}, %ID{} = left, %ID{} = right) do
{_, v, r} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
case Map.fetch(clocks, left.client) do
{:ok, c} -> c >= left.clock
_ -> false
end
end)

if v.id == left do
do_between(r, right, [v]) |> Enum.reverse()
else
[]
end
end

def add_after(%ArrayTree{ft: tree} = at, %Item{} = after_item, %Item{} = item) do
{l, v, r} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
case Map.fetch(clocks, after_item.id.client) do
{:ok, c} -> c >= after_item.id.clock
_ -> false
end
end)

if v == after_item do
{:ok,
%{
at
| ft:
l
|> FingerTree.conj(v)
|> then(fn tree ->
Enum.reduce(Item.explode(item), tree, fn item, tree ->
FingerTree.conj(tree, item)
end)
end)
|> FingerTree.append(r)
}}
else
{:error, "Item not found"}
end
end

def add_before(%ArrayTree{ft: %EmptyTree{} = ft} = at, _, %Item{} = item),
do:
{:ok,
%{
at
| ft: Enum.reduce(Item.explode(item), ft, fn item, ft -> FingerTree.conj(ft, item) end)
}}

def add_before(%ArrayTree{ft: tree} = at, %Item{} = before_item, %Item{} = item) do
{l, v, r} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
Map.fetch!(clocks, before_item.id.client) >= before_item.id.clock
end)

if v == before_item do
{:ok,
%{
at
| ft:
l
|> then(fn tree ->
Enum.reduce(Item.explode(item), tree, fn item, tree ->
FingerTree.conj(tree, item)
end)
end)
|> FingerTree.conj(v)
|> FingerTree.append(r)
}}
else
{:error, "Item not found"}
end
end

def next(%ArrayTree{ft: tree}, %Item{} = item) do
{_, v, r} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
case Map.fetch(clocks, item.id.client) do
{:ok, c} -> c >= item.id.clock
_ -> false
end
end)

if v == item, do: FingerTree.first(r)
end

def prev(%ArrayTree{ft: tree}, %Item{} = item) do
{l, v, _} =
FingerTree.split(tree, fn %{highest_clocks: clocks} ->
Map.fetch!(clocks, item.id.client) >= item.id.clock
end)

if v == item, do: FingerTree.last(l)
end

def first(%ArrayTree{ft: tree}), do: FingerTree.first(tree)
def last(%ArrayTree{ft: tree}), do: FingerTree.last(tree)
def rest(%ArrayTree{ft: tree} = array_tree), do: %{array_tree | ft: FingerTree.rest(tree)}
def butlast(%ArrayTree{ft: tree} = array_tree), do: %{array_tree | ft: FingerTree.butlast(tree)}

def length(%ArrayTree{ft: tree}) do
%Meter{len: len} = FingerTree.measure(tree)
len
end

def at(%ArrayTree{ft: %EmptyTree{}}, _index), do: nil

def at(%ArrayTree{ft: tree} = array_tree, index) do
if index > ArrayTree.length(array_tree) - 1 do
nil
else
{_, v, _} =
FingerTree.split(tree, fn %{len: len} ->
len > index
end)

v
end
end

defp do_between(%EmptyTree{}, _, acc), do: acc

defp do_between(tree, right, acc) do
f = FingerTree.first(tree)

if f.id == right do
[f | acc]
else
do_between(FingerTree.rest(tree), right, [f | acc])
end
end

defp meter_object do
FingerTree.MeterObject.new(
fn %Item{id: id} = item ->
Expand Down
Loading

0 comments on commit 6c7c74e

Please sign in to comment.