123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2023 TriliTech <contact@trili.tech> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)typekey=stringlistmoduleChildren=Map.Make(String)type'at={value:'aoption;children:'atChildren.t;keys_count:int;nodes_count:int;(* including empty one *)}letempty={value=None;children=Children.empty;keys_count=0;nodes_count=1}letnonexisting={value=None;children=Children.empty;keys_count=0;nodes_count=0}letreplace_valuetnew_v=matcht.valuewith|None->{twithvalue=Somenew_v;keys_count=t.keys_count+1}|Some_->{twithvalue=Somenew_v}letreplace_childtstepnew_c=letold_c=Option.value~default:nonexisting@@Children.findstept.childrenin{twithchildren=Children.addstepnew_ct.children;keys_count=t.keys_count-old_c.keys_count+new_c.keys_count;nodes_count=t.nodes_count-old_c.nodes_count+new_c.nodes_count;}letremove_childtstep=letold_c=Option.value~default:nonexisting@@Children.findstept.childrenin{twithchildren=Children.removestept.children;keys_count=t.keys_count-old_c.keys_count;nodes_count=t.nodes_count-old_c.nodes_count;}(* Helpers *)letguard_optb=ifbthenSome()elseNoneletis_key_readonly=function"readonly"::_->true|_->falseletreccreate_path(t:'at)f_tkey=matchkeywith|[]->f_tt|k::rest->letchild=Option.value~default:empty@@Children.findkt.childreninletnew_child=create_pathchildf_trestinreplace_childtknew_childletlookupkeyroot=letopenOption_syntaxinletreclookup_implt=function|[]->Somet|k::rest->let*child=Children.findkt.childreninlookup_implchildrestinlookup_implrootkeyletreadonly_guardkeyedit_readonly=guard_opt(ifis_key_readonlykeythenedit_readonlyelsetrue)(* Public functions.
Functions return Some if an operation has completed successfully.
*)letset_value~edit_readonlykeyvroot=letopenOption_syntaxinlet+()=readonly_guardkeyedit_readonlyincreate_pathroot(funt->replace_valuetv)keyletget_valuekeyroot=Option.bind(lookupkeyroot)(funx->x.value)letsubtrees_sizekeyroot=Option.fold~none:0~some:(funx->Children.cardinalx.children)@@lookupkeyrootletdelete~edit_readonlykeyroot=letopenOption_syntaxinlet*()=readonly_guardkeyedit_readonlyinletis_empty{value;children;_}=Option.is_nonevalue&&Children.is_emptychildrenin(* Return None if tree is not changed in result of deletion *)letrecdelete_tree_implt=function|[]->None|[k]->Some(remove_childtk)|k::rest->let*child=Children.findkt.childreninlet+new_child=delete_tree_implchildrestinletnew_t=remove_childtkin(* If k is the only child of t and has no value,
then we should "collapse" this branch *)ifis_emptynew_child&&is_emptynew_tthenempty(* If new_child is empty: we don't need to store it anymore *)elseifis_emptynew_childthennew_t(* Just replace old k with new one*)elsereplace_childtknew_childindelete_tree_implrootkeyletcopy_tree~edit_readonly~from_key~to_keyroot=letopenOption_syntaxinlet*from_t=lookupfrom_keyrootinlet+()=readonly_guardto_keyedit_readonlyincreate_pathroot(Fun.constfrom_t)to_keyletmove_tree~from_key~to_keyroot=letopenOption_syntaxinlet*()=guard_opt((not(is_key_readonlyfrom_key))&¬(is_key_readonlyto_key))inlet*from_t=lookupfrom_keyrootinlet+root=delete~edit_readonly:truefrom_keyrootincreate_pathroot(Fun.constfrom_t)to_key