Macaw.Layers.Fragment behaviour (macaw_layers v0.3.0)

Copy Markdown View Source

Fragments and stacking (spec/02): the fragment-type behavior, and the operations over fragments of a type.

A fragment is a document of a type, or any part of one: a map with the type's keys, not necessarily all of them and not validated. A document type that stacks implements this behavior, declaring what visualize read from its chart schema: how each key combines (key/2), which elements a layer replaces in place (identity/2), the kinds its fragments can be (kinds/0, wrapper/1) and where each kind lands in a whole document (place/3). The machinery holds no type's vocabulary, so every type combines the same way:

A struct is a leaf of every walk (visualize D-92): a %Macaw.Layers.Var{} stands for a value rather than being one. Only refs/2 and relocate/3 read a struct's parts, a %Macaw.Layers.Use{}'s, by name.

Ported from visualize's Visualize.Chart.Compose, Stack and Fragment.

Summary

Types

An id a store issued: {kind, n}.

A node kind of a document type.

A layer of a stack: a fragment, or a {name, fragment} pair.

How a key combines (spec/02 §2).

A key's spec: its rule, the node kind of a node-valued key, and the node kind of a list's or a name map's elements.

Callbacks

That value with its holes substituted, written back in the type's syntax.

A whole document as inline sites, one kind each.

The variables a value of the type's own syntax holds (a text's holes).

The identity of an element of a :concat list of kind elements, or nil.

Every id left at a reference site of a flattened document resolved through fetch.

How a key of a node of kind combines, or nil for a key the type does not know.

Every kind a fragment of the type can be, and so every kind an id can carry.

The type's own merge of a key whose rule is {:type, name}.

The two values a key of kind holds in both fragments, as they merge (spec/02 §3.3): a type expands a shorthand on one side to the part the other side gives, so the two merge key by key. Called only for a key both fragments hold.

Where a composite site of kind lands in a whole document (spec/02 §8.2).

A keyed site's contribution of no kind (a whole document, a multi-key fragment) as the layer that declares it under the site's key (spec/02 §8.2). Without it, such a contribution is its own layer.

The node kind of a whole document of the type.

One resolved site's contribution as the composite holding it shapes it, before it is placed (spec/02 §8.1): the composite node carries keys of the type's own besides uses and vars (visualize's defaults), and this is where they reach the sites.

What of a lower node of kind the higher one, merging over it, keeps (spec/02 §3.3): a node whose type changes keeps of the lower only what the new type still takes. A trim is a replacement, so a stack where one happens is its left fold (§3.2).

The kind a singular wrapper key names (style: → :style), or nil.

Functions

The left fold of compose/3; compose(type, []) is {:ok, %{}}.

b over a by every key's rule (spec/02 §3.1): {:ok, fragment}, or {:error, faults} with every :conflict at its path, ordered by path.

The value of one of this file's tags, its payload already read (spec/02 §9), for a type's decode_value/2 to call: {:ok, value}, or :default for a tag or payload it does not read, which the reader refuses at its path. An id's kind is matched against the type's kinds by its string, so no atom is made from a document.

The map form of this file's terms (spec/02 §9), for a type's encode_value/1 to call: a variable as {"$var": name}, an id as {"$id": [kind, n]}, a use site as {"$use": {…}}, its parts in the type's own map form; :default for any other term.

One entry per leaf path of stack/2 of layers (spec/02 §6.3), ordered by path: %{path, value, layer, overrode}, layer the layer's name or its zero-based position. It recomputes the stack by the cascade's own walk, so it cannot drift from it.

The kind of a fragment, derived (spec/02 §6.1): walking down while exactly one key is set, the last kind reached. A singular wrapper is the kind it names, a name map or list holding one entry the entry's kind, a composite :composite. nil for a fragment that branches at the top, which is no kind of fragment, and for one that reaches no kind below the root.

Whether the map at path in a document of the type is a name map (spec/02 §9), derived from the type's specs, for a type's Macaw.Layers.Form.name_map?/1 to call. Walking the path from the root node: a :union or :deep key holds a name map, whose every entry is a node of the key's element kind; a key with a node kind leads to a node of it, and so does a singular wrapper key at the root; a :concat key holds a list whose every element is a node of its element kind. Anything else is a leaf, never a name map.

Every id a fragment refers to, once each, sorted (spec/02 §6.2): a {kind, n} of the type's kinds/0 with n a positive integer, wherever it stands, a use site's ref, origin, local and vars included. Sorted, since the order a map is walked is the runtime's.

The fragment with every id rewritten through table, %{old => new}, and the ids the table did not carry, sorted (spec/02 §6.2). A use site's ids are rewritten in place, the site still a %Use{}; an id not in the table is left, to resolve as a mask.

The left fold of stack/3, the later layer the higher: stack(type, []) is %{} and stack(type, [a]) is a. A layer may be a {name, fragment} pair, whose name is ignored here and reported by explain/2.

The cascade of higher over lower (spec/02 §3.2): for every key the higher layer wins. It cannot fail, so it answers the fragment itself.

Types

id()

@type id() :: {kind(), pos_integer()}

An id a store issued: {kind, n}.

kind()

@type kind() :: atom()

A node kind of a document type.

layer()

@type layer() :: map() | {term(), map()}

A layer of a stack: a fragment, or a {name, fragment} pair.

rule()

@type rule() :: :concat | :union | :deep | :override | {:type, atom()}

How a key combines (spec/02 §2).

spec()

@type spec() :: %{merge: rule(), node: kind() | nil, element: kind() | nil}

A key's spec: its rule, the node kind of a node-valued key, and the node kind of a list's or a name map's elements.

Callbacks

bind_holes(kind, term, term, map)

(optional)
@callback bind_holes(kind(), term(), term(), map()) :: term()

That value with its holes substituted, written back in the type's syntax.

decompose(map)

(optional)
@callback decompose(map()) :: [Macaw.Layers.Use.t()]

A whole document as inline sites, one kind each.

holes(kind, term, term)

(optional)
@callback holes(kind(), term(), term()) :: [Macaw.Layers.Var.t()]

The variables a value of the type's own syntax holds (a text's holes).

identity(kind, map)

(optional)
@callback identity(kind(), map()) :: term()

The identity of an element of a :concat list of kind elements, or nil.

inline(map, function)

(optional)
@callback inline(map(), (id() -> {:ok, map()} | :error)) :: {map(), [{id(), list()}]}

Every id left at a reference site of a flattened document resolved through fetch.

key(kind, term)

@callback key(kind(), term()) :: spec() | nil

How a key of a node of kind combines, or nil for a key the type does not know.

kinds()

@callback kinds() :: [kind()]

Every kind a fragment of the type can be, and so every kind an id can carry.

merge(atom, term, term)

(optional)
@callback merge(atom(), term(), term()) :: term()

The type's own merge of a key whose rule is {:type, name}.

normalize(kind, term, lower, higher)

(optional)
@callback normalize(kind(), term(), lower :: term(), higher :: term()) :: {term(), term()}

The two values a key of kind holds in both fragments, as they merge (spec/02 §3.3): a type expands a shorthand on one side to the part the other side gives, so the two merge key by key. Called only for a key both fragments hold.

place(kind, map, arg3)

@callback place(kind(), map(), String.t() | nil) :: {:ok, map()} | :keyless

Where a composite site of kind lands in a whole document (spec/02 §8.2).

place_whole(contribution, key)

(optional)
@callback place_whole(contribution :: map(), key :: String.t()) :: {:ok, map()}

A keyed site's contribution of no kind (a whole document, a multi-key fragment) as the layer that declares it under the site's key (spec/02 §8.2). Without it, such a contribution is its own layer.

root()

@callback root() :: kind()

The node kind of a whole document of the type.

scope(composite, contribution, key)

(optional)
@callback scope(composite :: map(), contribution :: map(), key :: String.t() | nil) ::
  map()

One resolved site's contribution as the composite holding it shapes it, before it is placed (spec/02 §8.1): the composite node carries keys of the type's own besides uses and vars (visualize's defaults), and this is where they reach the sites.

under(kind, lower, higher)

(optional)
@callback under(kind(), lower :: map(), higher :: map()) :: map()

What of a lower node of kind the higher one, merging over it, keeps (spec/02 §3.3): a node whose type changes keeps of the lower only what the new type still takes. A trim is a replacement, so a stack where one happens is its left fold (§3.2).

wrapper(term)

@callback wrapper(term()) :: kind() | nil

The kind a singular wrapper key names (style: → :style), or nil.

Functions

compose(type, fragments)

@spec compose(module(), [map()]) ::
  {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The left fold of compose/3; compose(type, []) is {:ok, %{}}.

compose(type, a, b)

@spec compose(module(), map(), map()) ::
  {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}

b over a by every key's rule (spec/02 §3.1): {:ok, fragment}, or {:error, faults} with every :conflict at its path, ordered by path.

decode_value(type, arg2, name)

@spec decode_value(module(), String.t(), term()) :: {:ok, term()} | :default

The value of one of this file's tags, its payload already read (spec/02 §9), for a type's decode_value/2 to call: {:ok, value}, or :default for a tag or payload it does not read, which the reader refuses at its path. An id's kind is matched against the type's kinds by its string, so no atom is made from a document.

encode_value(type, use)

@spec encode_value(module(), term()) :: {:ok, term()} | :default

The map form of this file's terms (spec/02 §9), for a type's encode_value/1 to call: a variable as {"$var": name}, an id as {"$id": [kind, n]}, a use site as {"$use": {…}}, its parts in the type's own map form; :default for any other term.

explain(type, layers)

@spec explain(module(), [layer()]) :: [map()]

One entry per leaf path of stack/2 of layers (spec/02 §6.3), ordered by path: %{path, value, layer, overrode}, layer the layer's name or its zero-based position. It recomputes the stack by the cascade's own walk, so it cannot drift from it.

kind(type, fragment)

@spec kind(module(), map()) :: kind() | nil

The kind of a fragment, derived (spec/02 §6.1): walking down while exactly one key is set, the last kind reached. A singular wrapper is the kind it names, a name map or list holding one entry the entry's kind, a composite :composite. nil for a fragment that branches at the top, which is no kind of fragment, and for one that reaches no kind below the root.

name_map?(type, path)

@spec name_map?(module(), [term()]) :: boolean()

Whether the map at path in a document of the type is a name map (spec/02 §9), derived from the type's specs, for a type's Macaw.Layers.Form.name_map?/1 to call. Walking the path from the root node: a :union or :deep key holds a name map, whose every entry is a node of the key's element kind; a key with a node kind leads to a node of it, and so does a singular wrapper key at the root; a :concat key holds a list whose every element is a node of its element kind. Anything else is a leaf, never a name map.

Below a tag ({:tag, tag} first, spec/01 §6), a use site's local is a fragment of the type, so its maps are at the type's positions from the root; every other payload's maps are nodes.

iex> Macaw.Layers.Fragment.name_map?(Macaw.Layers.Theme.Document, [:theme, :styles])
true
iex> Macaw.Layers.Fragment.name_map?(Macaw.Layers.Theme.Document, [:theme, :styles, "x"])
false

refs(type, fragment)

@spec refs(module(), term()) :: [id()]

Every id a fragment refers to, once each, sorted (spec/02 §6.2): a {kind, n} of the type's kinds/0 with n a positive integer, wherever it stands, a use site's ref, origin, local and vars included. Sorted, since the order a map is walked is the runtime's.

relocate(type, fragment, table)

@spec relocate(module(), term(), %{required(id()) => id()}) :: {term(), [id()]}

The fragment with every id rewritten through table, %{old => new}, and the ids the table did not carry, sorted (spec/02 §6.2). A use site's ids are rewritten in place, the site still a %Use{}; an id not in the table is left, to resolve as a mask.

stack(type, list)

@spec stack(module(), [layer()]) :: map()

The left fold of stack/3, the later layer the higher: stack(type, []) is %{} and stack(type, [a]) is a. A layer may be a {name, fragment} pair, whose name is ignored here and reported by explain/2.

stack(type, lower, higher)

@spec stack(module(), map(), map()) :: map()

The cascade of higher over lower (spec/02 §3.2): for every key the higher layer wins. It cannot fail, so it answers the fragment itself.