/ Code.hs - generates the Code from a literate file

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:

  1. Filtering out all Prose
  2. Merging macro definitions with the same name
  3. Expanding macros and references to just lines of code
<< 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