Code is a module which transforms the main data structure
for a parsed lit
file into the actual source. By expanding all
macros and ignoring the narrative, the Code module produces the
portion intended for computer interpretation.lit
in theory is
language independent, as it does not make assumptions about the
language stored within. The arbitrary macro << * >>=
communicates
to Code the starting point of the document, comparable to a
table of contents. Code then proceeds to build the document expanding
macro references and joining macro extensions.
An overview of the file:
<< * >>=
<< define Code module >>
<< import modules >>
<< generate a code file from chunks >>
<< helper functions >>
Code only exports generate
akin to Html and Markdown, the other output modules.
<< define Code module >>=
{-# LANGUAGE OverloadedStrings #-}
module Code ( generate ) where
Code relies on a HashMap, where the keys are macro names and the values Chunk
.
Despite being of type Chunk
all values constructed from Prose
are ignored leaving
only Def
. For more information on types see Types.hs
<< import modules >>=
import Data.List (partition, intersperse)
import qualified Data.HashMap.Strict as Map
import qualified Data.Text as T
import Types
generate
is the interface for the module. lit
generates the [Chunk]
with Parse.hs
before calling generate
to make the code file from a lit
file. It performs:
Prose
<< generate a code file from chunks >>=
generate :: [Chunk] -> T.Text
generate = expand . merge . (filter isDef)
generate
depends on the following helper functions.
<< helper functions >>=
<< merge chunks with the same name >>
<< reduce chunks into one >>
<< expand chunk references with chunks >>
merge
allows for a macro extension. By reusing a macro definition,
narrative can be interweaved into code.
<< merge chunks with the same name >>=
merge :: [Chunk] -> [Chunk]
merge = mergeAux []
The same macro definition was used, and lit
will merge both into
one when it generates the final code file. merge
creates a list
of macros with the same name to be combined.
<< merge chunks with the same name >>=
mergeAux ans [] = ans
mergeAux ans (next:rest) =
let
name = getName next
chunkHasName name = (== name) . getName
(found, rem) = partition (chunkHasName name) rest
merged = combineChunks (next:found)
in
mergeAux (merged:ans) rem
combineChunks
assumes a list of macros with the same name. It returns
a new Chunk
that has the combined inner [Part]
of the list, but sharing
the line number and name of the first. Since combineChunks
reduces a list to
a value, it cannot be passed an empty list.
<< reduce chunks into one >>=
combineChunks :: [Chunk] -> Chunk
combineChunks (a:[]) = a
combineChunks l@(c:cs) = Def line name parts
where
parts = concatMap getParts l
name = getName c
line = getLineNo c
expand
assumes a list of only Def
of type Chunk
which hold the actual code
or references to other 'Def' which hold code. First it creates a map of (macro name, macro contents)
of more precisely (Text
,[Part]
). It then searches for the root macro name *
, resorting
to the first macro definition, and recursively expands the different parts of the root.
(<< * >>=
) to begin expansion
<< expand chunk references with chunks >>=
expand :: [Chunk] -> T.Text
expand chunks =
let
-- map (name, parts)
partMap = Map.fromList $ zip (map getName chunks) (map getParts chunks)
backup = getParts $ last chunks
parts = Map.lookupDefault backup "*" partMap
in
expandParts parts partMap T.empty
expand
from above relies on expandParts
to actually perform the recursive lookup. Each
call to expandParts
returns text for parts that are simply blocks of code or text which results
from expanding references to their appropriate code blocks. If the part is a reference (Ref name
),
expandParts
looks up the name of the macro in the map, and proceeds to pull out the code lines
in that macro. Refer to Types.hs for clarity on the different wrapper types mentioned here.
<< expand chunk references with chunks >>=
expandParts :: [Part] -> Map.HashMap T.Text [Part] -> T.Text -> T.Text
expandParts parts partMap baseIndent =
let
toText = (\part ->
case part of
Code txt -> T.append baseIndent txt
Ref name indent -> (expandParts refParts partMap (T.append baseIndent indent))
where refParts = Map.lookupDefault [] (T.strip name) partMap)
in
T.concat $ map toText parts