objective categorical abstract machine language personal data server
at main 226 lines 7.8 kB view raw
1open Lexicon_types 2 3(** returns SCCs in reverse topological order (dependencies first) 4 each SCC is a list of nodes *) 5let find_sccs (type node) (nodes : node list) ~(get_id : node -> string) 6 ~(get_deps : node -> string list) : node list list = 7 (* build node map: id -> node *) 8 let node_map = 9 List.fold_left (fun m node -> (get_id node, node) :: m) [] nodes 10 in 11 let node_ids = List.map get_id nodes in 12 (* build dependency map *) 13 let deps = List.map (fun node -> (get_id node, get_deps node)) nodes in 14 (* Tarjan's algorithm state *) 15 let index_counter = ref 0 in 16 let indices = Hashtbl.create 64 in 17 let lowlinks = Hashtbl.create 64 in 18 let on_stack = Hashtbl.create 64 in 19 let stack = ref [] in 20 let sccs = ref [] in 21 let rec strongconnect id = 22 let index = !index_counter in 23 incr index_counter ; 24 Hashtbl.add indices id index ; 25 Hashtbl.add lowlinks id index ; 26 Hashtbl.add on_stack id true ; 27 stack := id :: !stack ; 28 (* visit successors *) 29 let successors = 30 try List.assoc id deps |> List.filter (fun s -> List.mem s node_ids) 31 with Not_found -> [] 32 in 33 List.iter 34 (fun succ -> 35 if not (Hashtbl.mem indices succ) then begin 36 (* successor not yet visited *) 37 strongconnect succ ; 38 Hashtbl.replace lowlinks id 39 (min (Hashtbl.find lowlinks id) (Hashtbl.find lowlinks succ)) 40 end 41 else if Hashtbl.find_opt on_stack succ = Some true then 42 (* successor is on stack, part of current SCC *) 43 Hashtbl.replace lowlinks id 44 (min (Hashtbl.find lowlinks id) (Hashtbl.find indices succ)) ) 45 successors ; 46 (* if this is a root node, pop the SCC *) 47 if Hashtbl.find lowlinks id = Hashtbl.find indices id then begin 48 let rec pop_scc acc = 49 match !stack with 50 | [] -> 51 acc 52 | top :: rest -> 53 stack := rest ; 54 Hashtbl.replace on_stack top false ; 55 if top = id then top :: acc else pop_scc (top :: acc) 56 in 57 let scc_ids = pop_scc [] in 58 (* convert IDs to nodes, preserving original order *) 59 let scc_nodes = 60 List.filter_map 61 (fun n -> List.assoc_opt n node_map) 62 (List.filter (fun n -> List.mem n scc_ids) node_ids) 63 in 64 if scc_nodes <> [] then sccs := scc_nodes :: !sccs 65 end 66 in 67 (* run on all nodes *) 68 List.iter 69 (fun id -> if not (Hashtbl.mem indices id) then strongconnect id) 70 node_ids ; 71 (* SCCs are prepended, so reverse to get topological order *) 72 List.rev !sccs 73 74(** returns list of definition names that this type depends on within the same nsid *) 75let rec collect_local_refs nsid acc = function 76 | Array {items; _} -> 77 collect_local_refs nsid acc items 78 | Ref {ref_; _} -> 79 if String.length ref_ > 0 && ref_.[0] = '#' then 80 (* local ref: #foo *) 81 let def_name = String.sub ref_ 1 (String.length ref_ - 1) in 82 def_name :: acc 83 else begin 84 (* check if it's a self-reference: nsid#foo *) 85 match String.split_on_char '#' ref_ with 86 | [ext_nsid; def_name] when ext_nsid = nsid -> 87 def_name :: acc 88 | _ -> 89 acc 90 end 91 | Union {refs; _} -> 92 List.fold_left 93 (fun a r -> 94 if String.length r > 0 && r.[0] = '#' then 95 let def_name = String.sub r 1 (String.length r - 1) in 96 def_name :: a 97 else 98 match String.split_on_char '#' r with 99 | [ext_nsid; def_name] when ext_nsid = nsid -> 100 def_name :: a 101 | _ -> 102 a ) 103 acc refs 104 | Object {properties; _} -> 105 List.fold_left 106 (fun a (_, (prop : property)) -> collect_local_refs nsid a prop.type_def) 107 acc properties 108 | Record {record; _} -> 109 List.fold_left 110 (fun a (_, (prop : property)) -> collect_local_refs nsid a prop.type_def) 111 acc record.properties 112 | Query {parameters; output; _} -> ( 113 let acc = 114 match parameters with 115 | Some params -> 116 List.fold_left 117 (fun a (_, (prop : property)) -> 118 collect_local_refs nsid a prop.type_def ) 119 acc params.properties 120 | None -> 121 acc 122 in 123 match output with 124 | Some body -> 125 Option.fold ~none:acc ~some:(collect_local_refs nsid acc) body.schema 126 | None -> 127 acc ) 128 | Procedure {parameters; input; output; _} -> ( 129 let acc = 130 match parameters with 131 | Some params -> 132 List.fold_left 133 (fun a (_, (prop : property)) -> 134 collect_local_refs nsid a prop.type_def ) 135 acc params.properties 136 | None -> 137 acc 138 in 139 let acc = 140 match input with 141 | Some body -> 142 Option.fold ~none:acc 143 ~some:(collect_local_refs nsid acc) 144 body.schema 145 | None -> 146 acc 147 in 148 match output with 149 | Some body -> 150 Option.fold ~none:acc ~some:(collect_local_refs nsid acc) body.schema 151 | None -> 152 acc ) 153 | _ -> 154 acc 155 156(** find SCCs among definitions within a single lexicon 157 returns SCCs in reverse topological order *) 158let find_def_sccs nsid (defs : def_entry list) : def_entry list list = 159 find_sccs defs 160 ~get_id:(fun def -> def.name) 161 ~get_deps:(fun def -> collect_local_refs nsid [] def.type_def) 162 163(** get external nsid dependencies for a lexicon *) 164let get_external_nsids (doc : lexicon_doc) : string list = 165 let nsids = ref [] in 166 let add_nsid s = if not (List.mem s !nsids) then nsids := s :: !nsids in 167 let rec collect_from_type = function 168 | Array {items; _} -> 169 collect_from_type items 170 | Ref {ref_; _} -> 171 if String.length ref_ > 0 && ref_.[0] <> '#' then begin 172 match String.split_on_char '#' ref_ with 173 | ext_nsid :: _ -> 174 add_nsid ext_nsid 175 | [] -> 176 () 177 end 178 | Union {refs; _} -> 179 List.iter 180 (fun r -> 181 if String.length r > 0 && r.[0] <> '#' then 182 match String.split_on_char '#' r with 183 | ext_nsid :: _ -> 184 add_nsid ext_nsid 185 | [] -> 186 () ) 187 refs 188 | Object {properties; _} -> 189 List.iter 190 (fun (_, (prop : property)) -> collect_from_type prop.type_def) 191 properties 192 | Query {parameters; output; _} -> 193 Option.iter 194 (fun p -> 195 List.iter 196 (fun (_, (prop : property)) -> collect_from_type prop.type_def) 197 p.properties ) 198 parameters ; 199 Option.iter (fun o -> Option.iter collect_from_type o.schema) output 200 | Procedure {parameters; input; output; _} -> 201 Option.iter 202 (fun p -> 203 List.iter 204 (fun (_, (prop : property)) -> collect_from_type prop.type_def) 205 p.properties ) 206 parameters ; 207 Option.iter (fun i -> Option.iter collect_from_type i.schema) input ; 208 Option.iter (fun o -> Option.iter collect_from_type o.schema) output 209 | Record {record; _} -> 210 List.iter 211 (fun (_, (prop : property)) -> collect_from_type prop.type_def) 212 record.properties 213 | _ -> 214 () 215 in 216 List.iter (fun def -> collect_from_type def.type_def) doc.defs ; 217 !nsids 218 219(** find SCCs between lexicon files, in reverse topological order *) 220let find_file_sccs (lexicons : lexicon_doc list) : lexicon_doc list list = 221 let nsids = List.map (fun doc -> doc.id) lexicons in 222 find_sccs lexicons 223 ~get_id:(fun doc -> doc.id) 224 ~get_deps:(fun doc -> 225 (* filter to only include nsids we have *) 226 get_external_nsids doc |> List.filter (fun n -> List.mem n nsids) )