Skip to content

Architecture implementation

Martijn Schrage edited this page Mar 2, 2017 · 3 revisions

Each Proxima layer is structured in the same way. It consists of two components: a presentation module (with a name ending in Present) that defines a function presentIO, and an interpretation module (name ending in Interpret) that declares a function interpretIO. As described in the Architecture Overview, Proxima has 6 data levels with 5 layers in between:

Each level type is defined in a Haskell module:

Document $proxima/src/Evaluation/DocTypes.hs
Enriched Document $proxima/src/Evaluation/EnrTypes.hs
Presentation $proxima/src/Presentation/PresTypes.hs
Layout $proxima/src/Layout/LayTypes.hs
Arrangement $proxima/src/Arrangement/ArrTypes.hs
Rendererering $proxima/src/Rendering/RenTypes.hs

Because the Document and Enriched document type depend on the editor instance, the datatypes in DocTypes and EvalTypes have type parameters doc and enr, which denote the actual document and enriched document types. The actual types are declared in the instance-specific module DocTypes_Generated, which is generated from the DocumentTypeDefinition.prx.

The following Haskell modules implement the various layer components:

Evaluator $proxima/src/Evaluation/EvalPresent.hs
Reducer $proxima/src/Evaluation/EvalInterpret.hs
Presenter $proxima/src/Presentation/PresPresent.hs
Parser $proxima/src/Presentation/PresInterpret.hs
Layouter $proxima/src/Layout/LayPresent.hs
Scanner $proxima/src/Layout/LayInterpret.hs
Arranger $proxima/src/Arrangement/ArrPresent.hs
Unarranger $proxima/src/Arrangement/ArrInterpret.hs
Renderer $proxima/src/Rendering/RenPresent.hs
Gesture interpreter $proxima/src/Rendering/RenInterpret.hs

Starting with the edit gesture (which is regarded as an edit operation on the rendering for symmetry), the interpretation component of each layer maps the lower-level edit operation onto a higher-level edit operation. At the top the edit operation is sent downward, and each presentation component maps the higher-level edit operation onto a lower-level one. At the bottom left, this yields an edit operation on the HTML rendering.

In reality, a list of edit operations is passed up and down the architecture, and each component maps the head of the list onto a list of edit operations on the adjacent level. This way, each component can insert extra edit operations in the list. The reason for this is that sometimes a component cannot process an edit operation before some other operation is performed. For example, a navigation operation that moves out of the currently viewed area, may cause a previously unarranged part of the presentation to be arranged. After each cycle, the head of list (which is an update on the HTML rendering) is removed and another cycle is started if the list is not empty.

Edit operations in the presentation direction are in a datatype Edit<Level> (e.g. EditLayout or EditDocument), in the interpretation direction, the datatype names get a prime: Edit<Level>' (e.g. EditPresentation' or EditRendering').

Layer data flow

The data flow in a layer can be seen from the types of presentIO and interpretIO

presentIO ::   LayerState -> UpperLevel -> LowerLevel -> [EditUpperLevel] ->
             IO ([EditLowerLevel], LayerState, UpperLevel)

interpretIO :: LayerState -> LowerLevel -> UpperLevel -> [EditLowerLevel] ->
             IO ([EditUpperLevel], LayerState, LowerLevel)

In the sources, these functions sometimes have extra parameters, but when given these extra parameters, the types are as above. As mentioned above, only the head of that list is processed. Although it may seem that the previous value of the lower level is not needed, it is passed as a parameter, since the lower level may contain extra state that cannot be computed from the upper level and the edit operation on it. Extra state is discussed in detail in Chapter 4 of Martijn Schrage's PhD thesis.

The return values of presentIO are the list of edit operations on the lower level (only the head was actually mapped by present, the rest is mapped onto a lower level edit operation by the process of wrapping, explained below) together with the updated layer state and the updated upper level.

As was mentioned above, some layers have extra parameters at the front. The reason for this is that the mappings at several layers are specific to the editor instance and need to be passed to the generic system as parameters. Furthermore, some layers require the settings record which contains instance-specific settings. This record is also passed as a parameter. When the presentation or interpretation functions are applied to their extra parameters, they have the types shown above.

As an example, we can look at the presentation component. If we omit the type parameters, for brevity, the type of presentIO in PresPresent.hs is:

presentIO :: PresentationSheet ->
             LayerStatePres -> EnrichedDocLevel -> PresentationLevel -> [EditEnrichedDoc'] ->
             IO ([EditPresentation'], LayerStatePres, EnrichedDocLevel)

Which, when the PresentationSheet parameter is applied, corresponds to the type above.

Edit operations

Each level has a datatype for edit operations in the interpretation direction (Edit<Lvl>) and the presentation direction (Edit<Lvl>') (<Lvl> stands for the abbreviated level name). The <Level>Types module for each level declares two types Edit<Lvl>_ and Edit<Lvl>'_ (with underscores), which are used to declare Edit<Lvl> and Edit<Lvl>' in the module $proxima/src/Evaluation/DocTypes.hs. The reason for this is that the edit operation types need an extra parameter in order to support wrapping edit operations, which is explained below.

The Edit<Lvl>_ datatypes for the edit operations vary on each level, but a number of constructors is always present:

data Edit<Level> = Set<Lvl>_ (<Level>Level ...)
                 | Skip<Lvl> Int
                 | Wrap<Lvl> wrapped
                 | ...

At the layout level, for example, we have:

data EditLayout_ wrapped doc enr node clip token
                 = SetLay (LayoutLevel doc enr node clip token)
                 | SkipLay Int
                 | WrapLay wrapped
                 | ...

Set<Lvl>

The Set<Lvl> operation causes the source level (upper level for presentation, lower level for interpretation) to be replaced by the argument of set operation.

Skip<Lvl>

The Skip<Lvl> constructor is used in the interpretation direction to skip all layers above the current one and resume the presentation at the same layer. For example, when typing in an unparsed document, all layers above the layout layer can be skipped until the document is parsed.

In order to skip the layers above, an interpretation component can return a Skip<Lvl> 0 as the upper level edit operation. The interpretation component above does not compute any mapping but simply returns a skip operation with the number increased by one. At the top, the skip operation is sent down through the presentation components, which each decrease the number until 0 is reached. At that point (which is in the same layer that yielded the skip 0), normal presentation is resumed.

Wrap<Lvl>

The Wrap<Lvl> constructor is used for casting edit operations between layers. This allows a component to yield an edit operation not on the level immediately above it (on interpretation) or below it, but somewhere else in the cycle. A wrapped edit operation is passed around to the next layer until it arrives at the destination layer.

The argument of Wrap<Lvl> in the datatype Edit<Level_> is the type parameter wrapped, but its actual type in an edit operation is (Wrapped doc enr node clip token). The reason for the type variable is that because (Wrapped doc enr node clip token) depends on all edit operation datatypes, it cannot be used directly without introducing import cycles. Therefore, each edit operation datatype gets an underscore in its name and a type parameter wrapped, and in the module Wrap.hs, the actual edit operation types are declared. For example, for EditDocument, Wrapped has the type declaration:

type EditDocument doc enr node clip token =
       EditDocument_ (Wrapped doc enr node clip token) doc enr node clip token

Wrapping is implemented with a type class that has functions wrap and unwrap. In order to wrap an edit operation to an edit operation of another type, the function cast = unwrap . wrap can be used.

Clone this wiki locally