Accumulator
As noted in the overview, one uses an accumulator to collect information about sequentially numbered sections, cross-references, tables of content, etc., then uses a second accumulator to render the source text with these features. Accumulators are made of up of reducers and folds:
type alias Reducer a b : a -> b -> b
List.foldl : (a -> b -> b ) -> b -> List a -> bA Reducer is a name for the type of the first argument of a fold. Consider a reducer of the form
Reducer state a b = a -> (state, List b) -> (state, List b)It fits into a fold of the form
Reducer state a b -> (state, List b) -> List a -> (state, List b)Let transform have type StateReducer state a b. Define
acc transformer state_ inputList =
List.foldl transformer (state_, []) inputListThe type of this function is
Accumulator state a b = state -> List a -> (state, List b)To restate in plainer English, an Accumulator state a b takes as input a State a b and a List a and returns a tuple consisting of an updated State a b and another list, one of type List b.
Accumulator.parse
Let us discuss the accumulators used in MiniLatex. The first of these is Accumulator.parse. Its function is to take a LatexState and a list of paragraphs, i.e., a List String, and produce an updated LatexState and a List (List LatexExpression). Each element of the latter is a List LatexExpression representing the application of the MiniLatex parser to a paragraph.
Accumulator.parse :
LatexState
-> List String
-> ( LatexState, List (List LatexExpression) )
Accumulator.parse latexState paragraphs =
paragraphs
|> List.foldl parseReducer ( latexState, [] )The Accumulator.parse function applies a List.foldl to a pair consisting of an initial LatexState and an empty list using a parseReducer. The latter is defined as follows:
parseReducer :
String
-> ( LatexState, List (List LatexExpression) )
-> ( LatexState, List (List LatexExpression) )
parseReducer inputString ( latexState, inputList ) =
let
parsedInput =
Parser.parse inputString
newLatexState =
latexStateReducer parsedInput latexState
in
( newLatexState, inputList ++ [ parsedInput ] )The parseReducer takes as input a string, representing a logical paragraph of source text, and a pair consisting of a LatexState and a List (List LatexExpression. It parses the string and computes a new LatexState using latexStateReducer:
latexStateReducer : List LatexExpression -> LatexState -> LatexStateWe discuss this function later on. The return value of the parseReducer is a pair consisting of the new LatexState and inputList with the parsedInput appended.
Accumulator.render
Accumulator.render renderer is an accumulator which takes a LatexState and a List (List LatexExpression) as input, and produces a new LatexState and List a as output. The nature of a depends on the renderer function used:
Accumulator.render :
(LatexState -> List LatexExpression -> a)
-> LatexState
-> List (List LatexExpression)
-> ( LatexState, List a )
Accumulator.render renderer latexState paragraphs =
paragraphs
|> List.foldl (renderReducer renderer) ( latexState, [] )The expression renderReducer renderer is a reducer of type
List LatexExpression -> ( LatexState, List a ) -> ( LatexState, List a )It operates as follows:
Compute a new
LatexStateby applyinglatexStateReducerto (a) the given list ofLatexExpressionsand (b) theLatexStatecoming from the first element of the second argument, a tuple of type(LatexState, List a)Apply the
rendererto the new state computed in the previous step and theList LatexExpressiongiven by the first argument.Return a tuple consisting of the updated
LatexStateand theinputListwith the newly rendered text appended.
renderReducer :
(LatexState -> List LatexExpression -> a)
-> List LatexExpression
-> ( LatexState, List a )
-> ( LatexState, List a )
renderReducer renderer listLatexExpression ( latexState, inputList ) =
let
newLatexState =
latexStateReducer listLatexExpression latexState
renderedInput =
renderer newState listLatexExpression
in
( newLatexState, inputList ++ [ renderedInput ] )LatexStateReducer
The purpose of the latexStateReducer is to update a given LatexState using the information contained in a List LatexExpression, i.e., the parse result of a paragraph.
latexStateReducer : List LatexExpression -> LatexState -> LatexState
latexStateReducer parsedParagraph latexState =
let
theInfo =
parsedParagraph
|> List.head
|> Maybe.map info
|> Maybe.withDefault (LatexInfo "null" "null" [] [])
in
(latexStateReducerDispatcher theInfo) theInfo latexStateThis a fairly complex reducer. In brief, it looks at the first LatexExpression in the List LatexExpression representing a given paragraph, then uses the function info to compute a record, theInfo, which extracts certain information from the paragraph. For example, if the paragraph is \section{Introduction}, the theInfo will contatin fields name = section and typ = macro. The call latexStateReducerDispatcher theInfo uses a dictionary lookup to produce a function of type LatexInfo -> LatexState -> LatexState from theInfo. Therefore the value of the expression
(latexStateReducerDispatcher theInfo) theInfo latexStaeis of type LatexState, in accord with what one reads from the type annotation of latexStateReducer parsedParagraph latexState.
Let's see how this works in a simple example:
> import MiniLatex.Parser exposing(..)
> import Parser exposing(run)
> import MiniLatex.Accumulator exposing(info)
> result = run latexExpression "\\section{foo}"
Ok (Macro "section" [] [LatexList [LXString "foo"]])
> expression = Macro "section" [] [LatexList [LXString "foo"]]
> theInfo = info expression
{ name = "section", options = [], typ = "macro", value = [LatexList [LXString "Foo"]] }
: MiniLatex.Accumulator.LatexInfoNow the dispatcher looks like this
latexStateReducerDispatcher : LatexInfo -> (LatexInfo -> LatexState -> LatexState)
latexStateReducerDispatcher theInfo =
case Dict.get ( theInfo.typ, theInfo.name ) latexStateReducerDict of
Just f ->
f
Nothing ->
\latexInfo latexState -> latexStatewhere the dictionary has type
latexStateReducerDict : Dict.Dict ( String, String ) (LatexInfo -> LatexState -> LatexState)If the key given by theInfo is not in the latexStateReducerDict, projection of the arguments to LatexState is returned. In the case at hand, theInfo has fields name = "section" and typ = "macro", and the function returned by the dictionary is SRH.updateSectionNumber x y. Here SRH is an alias of the StateReducerHelper module. Referring to that module, we find that
updateSectionNumber : LatexInfo -> LatexState -> LatexState
updateSectionNumber info latexState =
let
label =
getCounter "s1" latexState |> (\x -> x + 1) |> String.fromInt
in
latexState
|> incrementCounter "s1"
|> updateCounter "s2" 0
|> updateCounter "s3" 0
|> addSection (PT.unpackString info.value) label 1Thus updateSectionNumber increments the "s1" counter in the latexState and sets the other section counters to zero. The addSection function, listed below, adds the current section to the table of contents field of the LatexState:
LatexState.addSection : String -> String -> Int -> LatexState -> LatexState
LatexState.addSection sectionName label level latexState =
let
newEntry =
TocEntry sectionName label level
toc =
latexState.tableOfContents ++ [ newEntry ]
in
{ latexState | tableOfContents = toc }We will not go down the rabbit hole any further, but you get the general idea. The LatexState holds counters, the table of contents, cross references, and a general-purpose dictionaray. To add new abilities to MiniLatex, one can add new entries to latexStateReducerDict or new fields to LatexState.
type alias LatexState =
{ counters : Counters
, crossReferences : CrossReferences
, tableOfContents : TableOfContents
, dictionary : Dictionary
}Example: rendering a section
To conclude this discussion, we exhibit the render for sections: s
renderSection : LatexState -> List LatexExpression -> Html msg
renderSection latexState args =
let
sectionName =
MiniLatex.Render.renderArg 0 latexState args
s1 =
getCounter "s1" latexState
label =
if s1 > 0 then
String.fromInt s1 ++ " "
else
""
ref =
idPhrase "section" sectionName
in
Html.h2 (headingStyle ref 24) [ Html.text <| label ++ sectionName ]Appendix: the latexStateReducerDict
latexStateReducerDict : Dict.Dict ( String, String ) (LatexInfo -> LatexState -> LatexState)
latexStateReducerDict =
Dict.fromList
[ ( ( "macro", "setcounter" ), \x y -> SRH.setSectionCounters x y )
, ( ( "macro", "section" ), \x y -> SRH.updateSectionNumber x y )
, ( ( "macro", "subsection" ), \x y -> SRH.updateSubsectionNumber x y )
, ( ( "macro", "subsubsection" ), \x y -> SRH.updateSubsubsectionNumber x y )
, ( ( "macro", "title" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "author" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "date" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "email" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "host" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "setclient" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "setdocid" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "macro", "revision" ), \x y -> SRH.setDictionaryItemForMacro x y )
, ( ( "env", "theorem" ), \x y -> SRH.setTheoremNumber x y )
, ( ( "env", "proposition" ), \x y -> SRH.setTheoremNumber x y )
, ( ( "env", "lemma" ), \x y -> SRH.setTheoremNumber x y )
, ( ( "env", "definition" ), \x y -> SRH.setTheoremNumber x y )
, ( ( "env", "corollary" ), \x y -> SRH.setTheoremNumber x y )
, ( ( "env", "equation" ), \x y -> SRH.setEquationNumber x y )
, ( ( "env", "align" ), \x y -> SRH.setEquationNumber x y )
, ( ( "smacro", "bibitem" ), \x y -> SRH.setBibItemXRef x y )
]Some conclusions
The reliance of Accumulator.parse and Accumulator.render on the LatexState record, as well as the latexStateReducerDict for dispatching calls to latexStateReducer, makes it very easy to add new features, e.g., new macros and environments whose rendering requires a computed state. Moreover, it is not hard to add fields to LatexState in order to add further new features. Indeed, the first version of LatexInfo had only a counters field. Others were added later as the scope of MiniLatex grew.
Last updated