free-foil-0.4.0: Efficient Type-Safe Capture-Avoiding Substitution for Free (Scoped Monads)
Safe HaskellNone
LanguageHaskell2010

Control.Monad.Free.Foil.Artifact

Description

Serialisation support for checked units: stored terms, spelling tables, name-range metadata, and relocation.

The machinery here assumes only that a unit's interned constants and its locals (the names its binders bind) occupy disjoint name ranges, and it checks that assumption from the recorded metadata rather than taking it on faith. A stored term is then meaningful verbatim: a local keeps its raw id, and a constant is resolved through a spelling table on load.

One policy that provides the disjointness globally and by construction is to keep constants below zero and locals at or above, which is what the guarded successor allocator protects.

What loading trusts, and what it checks, is the client's decision, and the functions here supply the checkable facts. checkStoredLayout judges the recorded ranges and constantRelocation judges the constants, both from metadata alone, so no stored term is ever walked for checking. Only relocateConstants walks a term, and only when a constant actually moved.

Synopsis

Errors

data ArtifactError ident Source #

What the machinery here can report. The type is parametric in the spelling, as the tables are, and a Functor over it.

Since: 0.4.0

Constructors

MalformedStoredTerm String

The bytes did not decode. The message is the decoder's.

OverlappingRegions NameRange NameRange

The recorded constants and locals ranges share a name.

SpellingForLocal RawName

The spelling table names something inside the locals region.

WrongDeclarationCount NameRange Int

The constants range does not hold one name per declaration.

UnknownConstant ident

A spelling the loading world does not know.

ConstantAmongLocals ident RawName

A relocation target inside the locals region, where the verbatim locals could capture it.

Instances

Instances details
Functor ArtifactError Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Methods

fmap :: (a -> b) -> ArtifactError a -> ArtifactError b #

(<$) :: a -> ArtifactError b -> ArtifactError a #

Show ident => Show (ArtifactError ident) Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Methods

showsPrec :: Int -> ArtifactError ident -> ShowS #

show :: ArtifactError ident -> String #

showList :: [ArtifactError ident] -> ShowS #

Eq ident => Eq (ArtifactError ident) Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Methods

(==) :: ArtifactError ident -> ArtifactError ident -> Bool #

(/=) :: ArtifactError ident -> ArtifactError ident -> Bool #

prettyArtifactError :: (ident -> String) -> ArtifactError ident -> String Source #

Render an error, given a renderer for the spellings.

Since: 0.4.0

Stored terms

newtype StoredTerm Source #

A term as stored: canonical bytes. Equality of stored terms is byte equality, which is what a canonical-artifact property tests.

Since: 0.4.0

Constructors

StoredTerm 

Instances

Instances details
Binary StoredTerm Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Generic StoredTerm Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Associated Types

type Rep StoredTerm 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

type Rep StoredTerm = D1 ('MetaData "StoredTerm" "Control.Monad.Free.Foil.Artifact" "free-foil-0.4.0-Bfr1HBuIt5nKrF1pl2NAYZ" 'True) (C1 ('MetaCons "StoredTerm" 'PrefixI 'True) (S1 ('MetaSel ('Just "storedBytes") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))
Show StoredTerm Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Eq StoredTerm Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

type Rep StoredTerm Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

type Rep StoredTerm = D1 ('MetaData "StoredTerm" "Control.Monad.Free.Foil.Artifact" "free-foil-0.4.0-Bfr1HBuIt5nKrF1pl2NAYZ" 'True) (C1 ('MetaCons "StoredTerm" 'PrefixI 'True) (S1 ('MetaSel ('Just "storedBytes") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString)))

storeTerm :: forall (binder :: S -> S -> Type) (sig :: Type -> Type -> Type) (n :: S). Binary (AST binder sig n) => AST binder sig n -> StoredTerm Source #

Store a term verbatim, through the instances of Control.Monad.Free.Foil.Binary.

The disjoint layout is what makes verbatim enough. A constant's spelling goes into the unit's table (termSpellings), and a local needs no table: its id is expected to be canonical, which it is when elaboration allocates locals in a region of their own.

Since: 0.4.0

decodeStored :: forall (binder :: S -> S -> Type) (sig :: Type -> Type -> Type) (n :: S) ident. Binary (AST binder sig n) => StoredTerm -> Either (ArtifactError ident) (AST binder sig n) Source #

Decode a stored term's bytes: the instances alone, no meaning yet. Meaning is given per unit, by constantRelocation and relocateConstants.

Since: 0.4.0

Spelling tables and locals

termSpellings Source #

Arguments

:: forall (n :: S) (binder :: S -> S -> Type) (sig :: Type -> Type -> Type) ident. (Distinct n, CoSinkable binder, Bifoldable sig) 
=> NameMap n ident

Spellings of the top-level names.

-> AST binder sig n 
-> Map RawName ident 

The spelling-table entries a term needs. Its free variables are exactly its constants, provided the stored declaration is closed over everything local. Each is mapped to its spelling from the display table.

Note that the table should cover the referenced constants and only those. A table of everything in scope would let an unused import dirty a dependant's content hash, and would differ between build schedules.

Since: 0.4.0

localsOf :: forall (sig :: Type -> Type -> Type) (binder :: S -> S -> Type) (n :: S). (Bifoldable sig, HasNameBinders binder) => AST binder sig n -> [RawName] Source #

The names a term's binders bind: what a unit's locals range covers. Note that supportOf cannot see them, since they are bound and not free.

Since: 0.4.0

spanOfNames :: [RawName] -> Maybe NameRange Source #

The tightest range covering the given names, or Nothing for none. The caller picks its own convention for the empty range.

>>> spanOfNames [7, 3, 5]
Just (NameRange {nameRangeLo = 3, nameRangeHi = 7})

Since: 0.4.0

Range metadata and its checks

data StoredLayout Source #

A unit's recorded name layout: the actual names of its own constants, and of its locals. The two travel together, so that they cannot be mixed up with the ranges of the loading world. An artifact records them as one field, and the checks and the relocation consume them as one value.

Since: 0.4.0

Instances

Instances details
Binary StoredLayout Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Generic StoredLayout Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Associated Types

type Rep StoredLayout 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

type Rep StoredLayout = D1 ('MetaData "StoredLayout" "Control.Monad.Free.Foil.Artifact" "free-foil-0.4.0-Bfr1HBuIt5nKrF1pl2NAYZ" 'False) (C1 ('MetaCons "StoredLayout" 'PrefixI 'True) (S1 ('MetaSel ('Just "storedConstants") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NameRange) :*: S1 ('MetaSel ('Just "storedLocals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NameRange)))
Show StoredLayout Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

Eq StoredLayout Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

type Rep StoredLayout Source # 
Instance details

Defined in Control.Monad.Free.Foil.Artifact

type Rep StoredLayout = D1 ('MetaData "StoredLayout" "Control.Monad.Free.Foil.Artifact" "free-foil-0.4.0-Bfr1HBuIt5nKrF1pl2NAYZ" 'False) (C1 ('MetaCons "StoredLayout" 'PrefixI 'True) (S1 ('MetaSel ('Just "storedConstants") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NameRange) :*: S1 ('MetaSel ('Just "storedLocals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NameRange)))

nameRangeSize :: NameRange -> Int Source #

How many names a range holds.

>>> nameRangeSize (NameRange 3 5)
3

Since: 0.4.0

nameRangeContains :: NameRange -> RawName -> Bool Source #

Whether a raw name lies in a range.

Since: 0.4.0

nameRangesOverlap :: NameRange -> NameRange -> Bool Source #

Whether two ranges share a name. An empty range overlaps nothing.

>>> nameRangesOverlap (NameRange 0 4) (NameRange 4 9)
True
>>> nameRangesOverlap (NameRange 0 4) (NameRange 5 9)
False

Since: 0.4.0

checkStoredLayout Source #

Arguments

:: StoredLayout

The unit's recorded layout.

-> Map RawName ident

Its spelling table.

-> Int

Its declaration count.

-> Either (ArtifactError ident) () 

The checks a unit's recorded layout admits, judged from metadata alone. The constants and locals ranges must not overlap, no spelling may be recorded for a local, and the constants range must hold exactly one name per declaration, since allocation is dense from the range's low end.

>>> layout = StoredLayout (NameRange (-10) (-9)) (NameRange 0 5)
>>> checkStoredLayout layout (Map.fromList [(-20, "P.base")]) 2
Right ()
>>> checkStoredLayout layout (Map.fromList [(3, "q")]) 2
Left (SpellingForLocal 3)

Since: 0.4.0

Relocation

constantRelocation Source #

Arguments

:: forall ident (n' :: S) (old :: S). Ord ident 
=> StoredLayout

The unit's recorded layout.

-> NameRange

The range this run assigned to the unit.

-> Map RawName ident

Its spelling table.

-> Map ident (Name n')

What each spelling means here.

-> Either (ArtifactError ident) (Maybe (NameMap old (Name n'))) 

What a unit's constants need in the loading world, judged once, from the spelling table alone. Nothing says every constant already has the id its spelling means here, which is the fast path, on which no term is walked at all. Otherwise the result is the renaming to apply. Its domain is a scope of the unit's world, which no longer exists, so its index is the caller's phantom.

The unit's own constants (table entries inside the recorded range) do not consult the world: they are being loaded right now, in the same order they were allocated, so their relocation is the affine shift between the recorded range and the one this run assigned. Their new names are thereby minted ahead of their allocation, which the caller's trust covers. An imported constant resolves by its spelling, and one this world does not know is reported. Finally, no relocation target may land among the locals, since verbatim locals rest on the two never meeting.

Since: 0.4.0

relocateConstants :: forall (binder :: S -> S -> Type) (sig :: Type -> Type -> Type) (n :: S) (n' :: S). Bifunctor sig => NameMap n (Name n') -> AST binder sig n -> AST binder sig n' Source #

Rename every constant reference through the map, moving the term from the unit's world into the loading one. This is the restriction to constants of a general renaming Name n -> Name n'. sinkabilityProof embodies the general renaming, but its efficient implementations degenerate the renaming to a coercion under binders, which is sound only for inclusions, whereas this walk carries an arbitrary map through.

The invariant that lets the walk ignore the binders is the disjointness the recorded layout certifies: every name in the map's domain is a constant, and a binder binds locals, so no binder can shadow a name in the domain and no local can be in it. Note that this covers imported constants too, since checkStoredLayout refuses a spelling for any name among the locals. Thus the map never needs extending under a binder, and locals and patterns cross by coercion. A constant outside the map (bytes referencing something the spelling table does not cover) is re-minted unchanged, trusted like everything else about the term. Note that a map that is the identity on raw ids would make the whole walk a coercion, which is why constantRelocation reports it as no relocation at all.

Since: 0.4.0