Skip to content

Commit

Permalink
refactor: factor out signatures to avoid duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
jonsterling committed Jul 8, 2022
1 parent b2375f9 commit 8f0175a
Show file tree
Hide file tree
Showing 9 changed files with 285 additions and 399 deletions.
20 changes: 1 addition & 19 deletions src/Language.mli
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,4 @@ type 'hook t =
| M_union of 'hook t list
| M_hook of 'hook

val equal : ('hook -> 'hook -> bool) -> 'hook t -> 'hook t -> bool

val any : 'hook t

val only : Trie.path -> 'hook t

val none : 'hook t
val except : Trie.path -> 'hook t
val in_ : Trie.path -> 'hook t -> 'hook t

val renaming : Trie.path -> Trie.path -> 'hook t

val seq : 'hook t list -> 'hook t

val union : 'hook t list -> 'hook t

val hook : 'hook -> 'hook t

val dump : (Format.formatter -> 'hook -> unit) -> Format.formatter -> 'hook t -> unit
include LanguageSigs.S with type 'hook t := 'hook t
68 changes: 68 additions & 0 deletions src/LanguageSigs.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module type S =
sig
(** {1 Types} *)

(** The abstract type of modifiers, parametrized by the type of hook labels. See {!val:hook} for hook labels.
Construct terms using builders in {!module:Language} and execute them using {!val:Modifier.S.modify}. *)
type 'hook t

(** Checking equality. *)
val equal : ('hook -> 'hook -> bool) -> 'hook t -> 'hook t -> bool

(** {1 Modifier Builders} *)

(** {2 Basics} *)

(** [any] keeps the content of the current tree. It is an error if the tree is empty (no name to match).
To avoid the emptiness checking, use the identity modifier {!val:seq}[ []].
This is equivalent to {!val:only}[ []]. *)
val any : 'hook t

(** [only path] keeps the subtree rooted at [path]. It is an error if the subtree was empty. *)
val only : Trie.path -> 'hook t

(** [in_ path m] runs the modifier [m] on the subtree rooted at [path]. Bindings outside the subtree are kept intact. For example, [in_ ["x"] ]{!val:any} will keep [y] (if existing), while {!val:only}[ ["x"]] will drop [y]. *)
val in_ : Trie.path -> 'hook t -> 'hook t

(** {2 Negation} *)

(** [none] drops everything. It is an error if the tree was already empty (nothing to drop).
To avid the emptiness checking, use the empty modifier {!val:union}[ []]. *)
val none : 'hook t

(** [except p] drops the subtree rooted at [p]. It is an error if there was nothing in the subtree. This is equivalent to {!val:in_}[ p ]{!val:none}. *)
val except : Trie.path -> 'hook t

(** {2 Renaming} *)

(** [renaming path path'] relocates the subtree rooted at [path] to [path']. The existing bindings at [path'] (if any) will be dropped.
It is an error if the subtree was empty (nothing to move). *)
val renaming : Trie.path -> Trie.path -> 'hook t

(** {2 Sequencing} *)

(** [seq [m0; m1; m2; ...; mn]] runs the modifiers [m0], [m1], [m2], ..., [mn] in order.
In particular, [seq []] is the identity modifier. *)
val seq : 'hook t list -> 'hook t

(** {2 Union} *)

(** [union [m0; m1; m2; ...; mn]] calculates the union of the results of individual modifiers [m0], [m1], [m2], ..., [mn].
In particular, [union []] is the empty modifier.
The {!field:Modifier.shadow} effect will be performed to resolve name conflicts,
with an intention for results of a modifier to shadow those of previous ones. *)
val union : 'hook t list -> 'hook t

(** {2 Custom Hooks} *)

(** [hook h] applies the hook labelled [h] to the entire trie
by performing the {!field:Modifier.hook} effect. *)
val hook : 'hook -> 'hook t

(** {2 Ugly Printing} *)

(** [dump dump_hook m] dumps the internal representation of [m] for debugging,
where [dump_hook] is the ugly printer for hook labels (see {!val:hook}). *)
val dump : (Format.formatter -> 'hook -> unit) -> Format.formatter -> 'hook t -> unit [@@ocaml.toplevel_printer]
end
38 changes: 5 additions & 33 deletions src/Modifier.ml
Original file line number Diff line number Diff line change
@@ -1,41 +1,14 @@
open Bwd
open BwdNotation
open ModifierSigs

module type Param =
sig
type data
type tag
type hook
type context
end

module type Handler =
sig
module P : Param
val not_found : P.context option -> Trie.bwd_path -> unit
val shadow : P.context option -> Trie.bwd_path -> P.data * P.tag -> P.data * P.tag -> P.data * P.tag
val hook : P.context option -> Trie.bwd_path -> P.hook -> (P.data, P.tag) Trie.t -> (P.data, P.tag) Trie.t
end


module type S =
sig
module P : Param
open P

val modify : ?context:context -> ?prefix:Trie.bwd_path -> hook Language.t -> (data, tag) Trie.t -> (data, tag) Trie.t

module Handle (H : Handler with module P := P) :
sig
val run : (unit -> 'a) -> 'a
val try_with : (unit -> 'a) -> 'a
end

module Perform : Handler with module P := P
end
module type Param = Param
module type Handler = Handler
module type S = ModifierSigs.S with module Language := Language

module Make (P : Param) : S with module P = P =
struct
module Language = Language
module P = P
include P

Expand Down Expand Up @@ -99,5 +72,4 @@ struct
module P = P
include Internal
end

end
37 changes: 5 additions & 32 deletions src/Modifier.mli
Original file line number Diff line number Diff line change
@@ -1,35 +1,8 @@
(* See Yuujinchou.mli for documentation. *)
open ModifierSigs

module type Param =
sig
type data
type tag
type hook
type context
end
module type Param = Param
module type Handler = Handler
module type S = ModifierSigs.S with module Language := Language

module type Handler =
sig
module P : Param
val not_found : P.context option -> Trie.bwd_path -> unit
val shadow : P.context option -> Trie.bwd_path -> P.data * P.tag -> P.data * P.tag -> P.data * P.tag
val hook : P.context option -> Trie.bwd_path -> P.hook -> (P.data, P.tag) Trie.t -> (P.data, P.tag) Trie.t
end

module type S =
sig
module P : Param
open P

val modify : ?context:context -> ?prefix:Trie.bwd_path -> hook Language.t -> (data, tag) Trie.t -> (data, tag) Trie.t

module Handle (H : Handler with module P := P) :
sig
val run : (unit -> 'a) -> 'a
val try_with : (unit -> 'a) -> 'a
end

module Perform : Handler with module P := P
end

module Make (P : Param) : S with module P = P
module Make (P : Param) : S with module P := P
71 changes: 71 additions & 0 deletions src/ModifierSigs.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

(** The parameters of an engine. *)
module type Param =
sig
(** The type of data held by the bindings. The difference between data and tags is that the data will survive the efficient retagging. See {!val:Trie.retag}. *)
type data

(** The type of tags attached to the bindings. The difference between data and tags is that tags can be efficiently reset. See {!val:Trie.retag}. *)
type tag

(** The type of modifier hook labels. This is for extending the modifier language. *)
type hook

(** The type of contexts passed to each call of {!val:Modifier.S.modify} for the effect handler to distinguish different function calls. *)
type context
end

module type Handler =
sig
module P : Param

val not_found : P.context option -> Trie.bwd_path -> unit
(** [not_found ctx prefix] is called when the engine expects at least one binding within the subtree at [prefix] but could not find any, where [ctx] is the context passed to {!val:S.modify}. Modifiers such as {!val:Language.any}, {!val:Language.only}, {!val:Language.none}, and a few other modifiers expect at least one matching binding. For example, the modifier {!val:Language.except}[ ["x"; "y"]] expects that there was already something under the subtree at [x.y]. If there were actually no names with the prefix [x.y], then the modifier will trigger this effect with [prefix] being [Emp #< "x" #< "y"]. *)

val shadow : P.context option -> Trie.bwd_path -> P.data * P.tag -> P.data * P.tag -> P.data * P.tag
(** [shadow ctx path x y] is called when item [y] is being assigned to [path] but [x] is already bound at [path], where [ctx] is the context passed to {!val:S.modify}. Modifiers such as {!val:Language.renaming} and {!val:Language.union} could lead to bindings having the same name, and when that happens, this function is called to resolve the conflicting bindings. To implement silent shadowing, one can simply return item [y]. One can also employ a more sophisticated strategy to implement type-directed disambiguation. *)


val hook : P.context option -> Trie.bwd_path -> P.hook -> (P.data, P.tag) Trie.t -> (P.data, P.tag) Trie.t
(** [hook prefix id input] is called when processing the modifiers created by {!val:Language.hook}, where [ctx] is the context passed to {!val:S.modify}. When the engine encounters the modifier {!val:Language.hook}[ id] while handling the subtree [input] at [prefix], it will call [hook prefix id input] and replace the existing subtree [input] with the return value. *)

end

module type S =
sig
module Language : LanguageSigs.S

module P : Param
open P
(** @closed *)

val modify : ?context:context -> ?prefix:Trie.bwd_path -> hook Language.t -> (data, tag) Trie.t -> (data, tag) Trie.t
(** [modify modifier trie] runs the [modifier] on the [trie] and return the transformed trie.
@param context The context sent to the effect handlers. If unspecified, effects come with {!constructor:None} as their context.
@param prefix The prefix prepended to any path or prefix sent to the effect handlers. The default is the empty path ([Emp]). *)

module Handle (H : Handler with module P := P) :
sig
val run : (unit -> 'a) -> 'a
(** [run f h] initializes the engine and runs the thunk [f], using [h] to handle modifier effects. See {!type:handler}. *)

val try_with : (unit -> 'a) -> 'a
(** [try_with f h] runs the thunk [f], using [h] to handle the intercepted modifier effects. See {!type:handler}.
Currently, [try_with] is an alias of {!val:run}, but [try_with] is intended to use within {!val:run} to intercept effects,
while {!val:run} is intended to be at the outermost layer to handle effects. That is, the following is the expected program structure:
{[
run @@ fun () ->
(* code *)
try_with f
(* more code *)
]}
*)
end

module Perform : Handler with module P := P
(** A handler that reperforms the effects. It can also be used to manually trigger the effects;
for example, [Perform.not_found (Emp #< "a" #< "b")] will perform the [not_found] effect
to be handled by the outer handler. *)
end
31 changes: 2 additions & 29 deletions src/Scope.ml
Original file line number Diff line number Diff line change
@@ -1,37 +1,10 @@
open Bwd
open BwdNotation
open ScopeSigs

module type Param = Modifier.Param
module type Handler = Modifier.Handler

module type S =
sig
module P : Param
open P

exception Locked

val resolve : Trie.path -> (data * tag) option
val include_singleton : ?context_visible:context -> ?context_export:context -> Trie.path * (data * tag) -> unit
val include_subtree : ?context_visible:context -> ?context_export:context -> Trie.path * (data, tag) Trie.t -> unit
val import_subtree : ?context:context -> Trie.path * (data, tag) Trie.t -> unit
val modify_visible : ?context:context -> hook Language.t -> unit
val modify_export : ?context:context -> hook Language.t -> unit
val modify : ?context:context -> ?prefix:Trie.bwd_path -> hook Language.t -> (data, tag) Trie.t -> (data, tag) Trie.t
val export_visible : ?context:context -> hook Language.t -> unit
val get_export : unit -> (data, tag) Trie.t

val section : ?context_visible:context -> ?context_export:context -> Trie.path -> (unit -> 'a) -> 'a

module Handle (H : Handler with module P := P) :
sig
val run : ?export_prefix:Trie.bwd_path -> ?init_visible:(data, tag) Trie.t -> (unit -> 'a) -> 'a
val try_with : (unit -> 'a) -> 'a
end

module Perform : Handler with module P := P
end

module type S = S with module Language := Language

module Make (P : Param) =
struct
Expand Down
36 changes: 5 additions & 31 deletions src/Scope.mli
Original file line number Diff line number Diff line change
@@ -1,34 +1,8 @@
(* See Yuujinchou.mli for documentation. *)
open ScopeSigs

module type Param = Modifier.Param
module type Handler = Modifier.Handler
module type Param = Param
module type Handler = Handler
module type S = S with module Language := Language

module type S =
sig
module P : Param
open P

exception Locked

val resolve : Trie.path -> (data * tag) option
val include_singleton : ?context_visible:context -> ?context_export:context -> Trie.path * (data * tag) -> unit
val include_subtree : ?context_visible:context -> ?context_export:context -> Trie.path * (data, tag) Trie.t -> unit
val import_subtree : ?context:context -> Trie.path * (data, tag) Trie.t -> unit
val modify_visible : ?context:context -> hook Language.t -> unit
val modify_export : ?context:context -> hook Language.t -> unit
val modify : ?context:context -> ?prefix:Trie.bwd_path -> hook Language.t -> (data, tag) Trie.t -> (data, tag) Trie.t
val export_visible : ?context:context -> hook Language.t -> unit
val get_export : unit -> (data, tag) Trie.t

val section : ?context_visible:context -> ?context_export:context -> Trie.path -> (unit -> 'a) -> 'a

module Handle (H : Handler with module P := P) :
sig
val run : ?export_prefix:Trie.bwd_path -> ?init_visible:(data, tag) Trie.t -> (unit -> 'a) -> 'a
val try_with : (unit -> 'a) -> 'a
end

module Perform : Handler with module P := P
end

module Make (P : Param) : S with module P = P
module Make (P : Param) : S with module P := P
Loading

0 comments on commit 8f0175a

Please sign in to comment.