packages feed

hampp (empty) → 0.2

raw patch · 3 files changed

+723/−0 lines, 3 filesdep +basedep +bytestringdep +containerssetup-changed

Dependencies added: base, bytestring, containers, directory, filepath, preprocessor-tools

Files

+ Main.lhs view
@@ -0,0 +1,682 @@+% Hampp
+% version 0.2
+% Public domain
+
+\input birdstyle
+
+\birdleftrule=1pt
+\emergencystretch=1em
+
+\def\hugebreak{\penalty-600\vskip 30pt plus 8pt minus 4pt\relax}
+\newcount\chapno
+\def\: #1.{\advance\chapno by 1\relax\hugebreak{\bf\S\the\chapno. #1. }}
+
+\: Introduction. This is preprocessor for Haskell programming, supporting
+macros, named chunks, include files, and a few other features. (I intend
+to include more features in future.)
+
+> module Main (main) where {
+
+Imports:
+
+> import Control.Applicative;
+> import Control.Monad;
+> import Data.List;
+> import Data.Map (Map);
+> import qualified Data.Map as M;
+> import Data.Monoid;
+> import Debug.Trace;
+> import Language.Haskell.Preprocessor;
+> import Language.Haskell.Preprocessor.Error (Error);
+> import Language.Haskell.Preprocessor.Loc;
+> import System.Directory;
+> import System.Environment;
+> import System.FilePath;
+> import System.IO;
+> import System.IO.Error;
+
+\: Utility Functions.
+
+> instance Alternative IO where {
+>   empty = fail [];
+>   x <|> y = catch x $ \e -> modifyIOError
+>    (\z -> if z == userError [] then e else z) y;
+> };
+
+> bool :: x -> x -> Bool -> x;
+> bool x _ False = x;
+> bool _ x True = x;
+
+> choice :: Alternative f => [f x] -> f x;
+> choice = foldr (<|>) empty;
+
+> (<>>=) :: (Functor m, Monad m) => m a -> (a -> m b) -> m a;
+> x <>>= f = x >>= ap (<$) f;
+> infixl 1 <>>=;
+
+> transEnum :: (Enum x, Enum y) => x -> y;
+> transEnum = toEnum . fromEnum;
+
+> whenMaybe :: Applicative f => Maybe x -> (x -> f ()) -> f ();
+> whenMaybe x f = maybe (pure ()) f x;
+
+> mvAppend :: (Ord x, Monoid y) => x -> y -> Map x y -> Map x y;
+> mvAppend = M.insertWith mappend;
+
+> mvChange :: (Ord x, Monoid y) => x -> (y -> y) -> Map x y -> Map x y;
+> mvChange k f = M.insertWith (flip $ const . f) k (f mempty);
+
+> dropWhileEnd :: (a -> Bool) -> [a] -> [a];
+> dropWhileEnd p = foldr
+>  (\x xs -> if p x && null xs then [] else x : xs) [];
+
+> ltrim :: String -> String;
+> ltrim = dropWhile (' ' >=);
+
+> rtrim :: String -> String;
+> rtrim = dropWhileEnd (' ' >=);
+
+\: Include Files. First step is replacing include files.
+
+> theSearchPath :: IO [FilePath];
+> theSearchPath = (splitSearchPath <$> getEnv "HAMPPPATH") <|> pure ["."];
+
+> includeFiles :: [FilePath] -> [Ast] -> IO [Ast];
+> includeFiles _ [] = return [];
+> includeFiles p (Single (Token { val = '#':'I':' ':v }) : t) =
+>  liftA2 (++) (includeOne p $ rtrim v) (includeFiles p t);
+> includeFiles p (Single (Token { val = '#':'J':' ':v }) : t) =
+>  liftA2 (++) (includeMany $ rtrim v) (includeFiles p t);
+> includeFiles p (x @ Block { body = v } : t) = liftA2
+>  (\y -> (x { body = y } :)) (includeFiles p v) (includeFiles p t);
+> includeFiles p (h : t) = (h :) <$> includeFiles p t;
+
+> runIncludeFiles :: [Ast] -> IO [Ast];
+> runIncludeFiles a = theSearchPath >>= \p -> includeFiles p a;
+
+> includeOne :: [FilePath] -> FilePath -> IO [Ast];
+> includeOne p f = choice ((fileLoadX mySpec f . (</> f)) <$> p)
+>  >>= either (fail . show) (includeFiles p);
+
+To look for wildcard files, it will use only the current directory, or a
+different directory if that is specified in the filename to include. Note
+that on Windows, it must match case insensitive, and on UNIX, it has to be
+case sensitive.
+
+> includeMany :: FilePath -> IO [Ast];
+> includeMany f = error "Wildcard includes not currently implemented";
+
+Some files are literate Haskell, or other formats, so it must know how to
+load them differently.
+
+> data FileType = Haskell | LiterateHaskell;
+
+> fileType :: String -> FileType;
+> fileType ".hs" = Haskell;
+> fileType ".inc" = Haskell;
+> fileType ".lhs" = LiterateHaskell;
+> fileType ".linc" = LiterateHaskell;
+> fileType ".tex" = LiterateHaskell;
+> fileType x = error ("Unknown file extension: " ++ x);
+
+> fileLoadX :: SynSpec -> String -> FilePath -> IO (Either Error [Ast]);
+> fileLoadX sp nm fp = case fileType (takeExtension fp) of {
+>   Haskell -> fileLoad sp nm fp;
+>   LiterateHaskell -> parseBy sp nm . unliterate <$> readFile fp;
+> };
+
+Currently this function is only for bird-style program.
+
+> unliterate :: String -> String;
+> unliterate = unlines . fmap (\x -> case x of {
+>   '>' : t -> ' ' : t;
+>   _ -> [];
+> }) . lines;
+
+\: Parsing Corrections. A few things from the preprocessor package have
+some mistakes, which can be corrected here. Some functions are also
+missing, which I can correct in this way.
+
+This function {\tt adjacent} to check if two tokens are adjacent, without
+any spaces in between.
+
+> adjacent :: Token -> Token -> Bool;
+> adjacent xt yt = let { x = getLoc xt; y = getLoc yt; } in
+>  not (isBogus x) && file x == file y && line x == line y &&
+>  col y >= col x && col y - col x <= length (val xt);
+
+Fixes include qualified names/operators, octal/hexadecimal literals, and
+template haskell name quotations (syntax is changed to {\tt.} instead of
+{\tt'} in front of names). The feature to use binary integer literals has
+been added. In addition, trailing spaces are removed from C-like
+directives, and words with {\tt8} at the start are special commands for
+the macro processor in this program.
+
+> fixAst :: [Ast] -> [Ast];
+> fixAst [] = [];
+> fixAst (Single (x @ Token { tag = Variable, val = n }) :
+>  Single (y @ Token { tag = Operator, val = '.' : o @ (_ : _) }) : t)
+>  | adjacent x y = Single (x { tag = Operator, val = n ++ ('.' : o) })
+>  : fixAst t;
+> fixAst (Single (x @ Token { tag = Variable, val = n }) :
+>  Single (y @ Token { tag = Operator, val = "." }) :
+>  Single (z @ Token { tag = Variable, val = v }) : t) | adjacent x y &&
+>  adjacent y z = fixAst
+>  (Single (z { tag = Variable, val = n ++ "." ++ v }) : t);
+> fixAst (Single (x @ Token { val = "0" }) :
+>  Single (y @ Token { tag = Variable, val = v }) : t) | adjacent x y =
+>  case (head v) of {
+>    'x' -> Single (x { val = '0' : v }) : fixAst t;
+>    'o' -> Single (x { val = '0' : v }) : fixAst t;
+>    'b' -> Single (x { val = show $ readBinaryInt (tail v) }) : fixAst t;
+>    _ -> Single x : Single y : fixAst t;
+>  };
+> fixAst (Single (x @ Token { val = "0" }) :
+>  Single (y @ Token { tag = CharLit, val = v }) : t) | adjacent x y =
+>  Single (x { val = show $ fromEnum (read v :: Char) }) : fixAst t;
+> fixAst (Single (x @ Token { val = "8" }) :
+>  Single (y @ Token { tag = Variable, val = v }) : t) | adjacent x y =
+>  Single (x { tag = Variable, val = '8' : v }) : fixAst t;
+> fixAst (Single (x @ Token { val = "." }) :
+>  Single (y @ Token { tag = Variable, val = v }) : t) | adjacent x y =
+>  Single (x { tag = Variable, val = '\'' : v }) : fixAst t;
+> fixAst (Single (x @ Token { val = ".." }) :
+>  Single (y @ Token { tag = Variable, val = v }) : t) | adjacent x y =
+>  Single (x { tag = Variable, val = '\'' : '\'' : v }) : fixAst t;
+> fixAst (x @ Block { body = v } : t) = x { body = fixAst v } : fixAst t;
+> fixAst (Single (x @ Token { tag = CPragma, val = v }) : t) =
+>  Single (x { val = rtrim v }) : fixAst t;
+> fixAst (h : t) = h : fixAst t;
+
+> readBinaryInt :: String -> Integer;
+> readBinaryInt = f . reverse where {
+>   f [] = 0;
+>   f ('0' : x) = 2 * f x;
+>   f ('1' : x) = succ (2 * f x);
+> };
+
+The default spec is changed.
+
+> mySpec :: SynSpec;
+> mySpec = SynSpec {
+>   unboxed = True,
+>   pragmas = False,
+>   levelnest = False,
+>   blocks = [
+>     pair "(" ")",
+>     pair "[" "]",
+>     pair "{" "}"
+>   ]
+> };
+
+\: Reordering. After {\tt fixAst}, reordering of sections is done, in a
+similar way to WEB.
+
+First split.
+
+> splitToSections :: String -> Int -> String -> [Ast] -> Map String [Ast];
+> splitToSections _ _ _ [] = M.empty;
+> splitToSections q i _ (Single (Token { val = '#':'P':_ }) : t) =
+>  splitToSections q i q t;
+> splitToSections _ i cur (Single (Token { val = '#':'\SO':v }) : t) =
+>  splitToSections v i v t;
+> splitToSections q i _ (Single (Token { val = '#':'{':v }) : t) =
+>  splitToSections q i (init v) t;
+> splitToSections q i cur ((x @ Block { body = b @ (_ : _ : _) }) : t) =
+>  mvAppend cur [x { body = [Single $
+>  newToken { val = '\SI' : show i }] }] $
+>  splitToSections (show i) (succ i) (show i)
+>  (b ++ (Single (newToken { val = '#':'\SO':cur }) : t));
+> splitToSections q i cur (h : t) = mvAppend cur [h]
+>  $ splitToSections q i cur t;
+
+And then, recombine.
+
+> recombineSections :: Map String [Ast] -> [Ast] -> [Ast];
+> recombineSections _ [] = [];
+> recombineSections m (Block { item = Token { val = "{" },
+>  body = [Single (Token { tag = StringLit, val = v })] } : t) =
+>  recombineSections m ((m M.! v) ++ t);
+> recombineSections m (Single (Token { val = '\SI' : v }) : t) =
+>  recombineSections m ((m M.! v) ++ t);
+> recombineSections m (h @ Block { body = b } : t) =
+>  h { body = recombineSections m b } : recombineSections m t;
+> recombineSections m (h : t) = h : recombineSections m t;
+
+Do together.
+
+> reorderProg :: [Ast] -> [Ast];
+> reorderProg = (recombineSections <*> (M.! [])) . splitToSections []0[];
+
+\: Macro Processing.
+
+> type MacroState x = (Map String [Ast], x);
+
+> macroProc :: MacroState [Ast] -> [Ast];
+> macroProc (_, []) = [];
+> macroProc (mac, Single(x @ Token { tag = CPragma, val = '#':'D':' ':v })
+>  : t) = let { (a, b) = break (' ' >=) v; } in macroProc (M.insert a
+>  (either (error . show) fixAst $ parseBy mySpec "<macro>" b) mac, t);
+> macroProc (mac, Single (x @ Token { tag = Variable, val = v }) : t)
+>  | M.member v mac = macroProc (mac, (mac M.! v) ++ t);
+> macroProc (mac, Single (x @ Token { tag = Variable, val = '8':v }) : t)
+>  = macroProc $ macroCmd v (mac, t);
+> macroProc (mac, Single (x @ Token { val = '\DLE' : v }) : t)
+>  = Single (x { val = v }) : macroProc (mac, t);
+> macroProc (mac, h @ Block { body = b } : t) =
+>  h { body = macroProc (mac, b) } : macroProc (mac, t);
+> macroProc (mac, h : t) = h : macroProc (mac, t);
+
+> macroCmd :: String -> MacroState [Ast] -> MacroState [Ast];
+
+{\tt8append}: Append token to macro.
+
+> macroCmd "append" (mac, Single (Token { val = v }) : h : t) =
+>  (mvAppend v [h] mac, t);
+
+{\tt8appendblock}: Append many tokens to macro.
+
+> macroCmd "appendblock" (mac, Single (Token { val = v })
+>  : Block { body = b } : t) = (mvAppend v b mac, t);
+
+{\tt8appendcopy}: Append copy of another macro to a macro.
+
+> macroCmd "appendcopy" (mac, Single (Token { val = v1 }) :
+>  Single (Token { val = v2 }) : t) = (mvAppend v1 (mac M.! v2) mac, t);
+
+{\tt8calculate}: Perform calculation block (see next chapter).
+
+> macroCmd "calculate" (mac, Block { body = b } : t) =
+>  (mac, calcAst [] (macroProc (mac, b)) ++ t);
+
+{\tt8close}: Close a block.
+
+> macroCmd "close" (mac, t) = (mac, Single (newToken { val = "}" }) : t);
+
+{\tt8copy}: Copy macro.
+
+> macroCmd "copy" (mac, Single (Token { val = v1 }) :
+>  Single (Token { val = v2 }) : t) = (M.insert v1 (mac M.! v2) mac, t);
+
+{\tt8data}: Make list of constructors to data constructor declarations.
+
+> macroCmd "data" (mac, Single (Token { val = v }) : t) =
+>  (mac, case (macDeclareData [] (mac M.! v)) of {
+>  [] -> []; _ : x -> Single (newToken { val = "=" }) : x; } ++ t);
+
+{\tt8debug\char`_macros}: Show all macros for debugging.
+
+> macroCmd "debug_macros" (mac, t) = (mac, traceShow mac t);
+
+{\tt8debug\char`_show}: Show a message for debugging.
+
+> macroCmd "debug_show" (mac, h : t) = (mac, traceShow h t);
+
+{\tt8decrement}: Decrement numeric tokens in macro.
+
+> macroCmd "decrement" (mac, Single (Token { val = v }) : t) =
+>  (mvChange v decrAst mac, t);
+
+{\tt8define}: Define a macro to a single token.
+
+> macroCmd "define" (mac, Single (Token { val = v }) : h : t) =
+>  (M.insert v [h] mac, t);
+
+{\tt8defineblock}: Define a macro to many tokens.
+
+> macroCmd "defineblock" (mac, Single (Token { val = v })
+>  : Block { body = b } : t) = (M.insert v b mac, t);
+
+{\tt8delete}: Delete macro.
+
+> macroCmd "delete" (mac, Single (Token { val = v }) : t) =
+>  (M.delete v mac, t);
+
+{\tt8expand}: Expand token and create macro.
+
+> macroCmd "expand" (mac, Single (Token { val = v }) : h : t) =
+>  (M.insert v (macroProc (mac, [h])) mac, t);
+
+{\tt8fieldlist}: Make list of fields with commas instead of semicolons,
+hiding anything other than the one with type signature.
+
+> macroCmd "fieldlist" (mac, Single (Token { val = v }) : t) =
+>  (mac, (macFieldList $ splitSemis (mac M.! v)) ++ t);
+
+{\tt8fieldval}: Make list of fields with commas instead of semicolons,
+keeping only value equations rather than type signatures.
+
+> macroCmd "fieldval" (mac, Single (Token { val = v }) : t) =
+>  (mac, (macFieldVal "," $ splitSemis (mac M.! v)) ++ t);
+
+{\tt8fieldvalues}: Similar to above but keep semicolons.
+
+> macroCmd "fieldvalues" (mac, Single (Token { val = v }) : t) =
+>  (mac, (macFieldVal ";" $ splitSemis (mac M.! v)) ++ t);
+
+{\tt8global}: Cause macros in block to affect global state.
+
+> macroCmd "global" (mac, Block { item = i, body = b, next = n } : t) =
+>  (mac, Single i : b ++ (n : t));
+
+{\tt8ifdef}: True if macro is defined.
+
+> macroCmd "ifdef" (mac, Single (Token { val = v }) : h : t) =
+>  (mac, bool t (h : t) (M.member v mac));
+
+{\tt8ifeq}: True if macros are equivalent.
+
+> macroCmd "ifeq" (mac, Single (Token { val = v1 }) :
+>  Single (Token { val = v2 }) : h : t) =
+>  (mac, bool t (h : t) (M.lookup v1 mac == M.lookup v2 mac));
+
+{\tt8ifndef}: True if macro is not defined.
+
+> macroCmd "ifndef" (mac, Single (Token { val = v }) : h : t) =
+>  (mac, bool (h : t) t (M.member v mac));
+
+{\tt8ifeq}: True if macros are not equivalent.
+
+> macroCmd "ifneq" (mac, Single (Token { val = v1 }) :
+>  Single (Token { val = v2 }) : h : t) =
+>  (mac, bool t (h : t) (M.lookup v1 mac /= M.lookup v2 mac));
+
+{\tt8increment}: Increment numeric tokens in macro.
+
+> macroCmd "increment" (mac, Single (Token { val = v }) : t) =
+>  (mvChange v incrAst mac, t);
+
+{\tt8local}: Remove delimiters from block while keeping macro effects
+local.
+
+> macroCmd "local" (mac, h : t) =
+>  (mac, h { item = Token Comment [] bogus [], next = Empty } : t);
+
+{\tt8localvar}: Call a macro locally with a parameter.
+
+> macroCmd "localvar" (mac, Single (Token { val = n }) : x : y : t) =
+>  (mac, macroProc (M.insert n [y] mac, [x]) ++ t);
+
+{\tt8open}: Open a block.
+
+> macroCmd "open" (mac, t) = (mac, Single (newToken { val = "{" }) : t);
+
+{\tt8quote}: Turn off expansion.
+
+> macroCmd "quote" (mac, Single (x @ Token { val = v }) : t) =
+>  (mac, Single (x { val = '\DLE' : v }) : t);
+
+{\tt8quotestring}: Change a string into a token without expansion.
+
+> macroCmd "quotestring" (mac, Single (x @ Token { val = v }) : t) =
+>  (mac, Single (x { val = '\DLE' : read v }) : t);
+
+{\tt8readstring}: Change a string into a token with expansion.
+
+> macroCmd "readstring" (mac, Single (x @ Token { val = v }) : t) =
+>  (mac, Single (x { val = read v }) : t);
+
+{\tt8string}: Convert token to string literal.
+
+> macroCmd "string" (mac, Single (x @ Token { val = v }) : t) =
+>  (mac, Single (x { tag = StringLit, val = show v }) : t);
+
+Everything else is error.
+
+> macroCmd x _ = error $ "Unknown macro 8" ++ x;
+
+Others:
+
+> incrAst :: [Ast] -> [Ast];
+> incrAst [] = [];
+> incrAst (Single (x @ Token { tag = IntLit, val = v }) : t) =
+>  Single (x { val = show (succ (read v :: Integer)) }) : incrAst t;
+> incrAst (h : t) = h : incrAst t;
+
+> decrAst :: [Ast] -> [Ast];
+> decrAst [] = [];
+> decrAst (Single (x @ Token { tag = IntLit, val = v }) : t) =
+>  Single (x { val = show (pred (read v :: Integer)) }) : decrAst t;
+> decrAst (h : t) = h : decrAst t;
+
+> macDeclareData :: [String] -> [Ast] -> [Ast];
+> macDeclareData _ [] = [];
+> macDeclareData s (Single x : t) = bool [Single $ newToken { val = "|" },
+>  Single x] [] (elem (val x) s) ++ macDeclareData (val x : s) t;
+> macDeclareData s (Block { body = b } : t) =
+>  let { Single x : _ = b; } in bool (Single
+>  (newToken { val = "|" }) : b) [] (elem (val x) s)
+>  ++ macDeclareData (val x : s) t;
+> macDeclareData s (Empty : t) = macDeclareData s t;
+
+> macFieldList :: [[Ast]] -> [Ast];
+> macFieldList [] = [];
+> macFieldList ([] : t) = macFieldList t;
+> macFieldList (h : t) = bool [] (h ++ [Single $ newToken { val = "," }])
+>  (any (valIs "::") h && not (any (valIs "=") h))
+>  ++ macFieldList t;
+
+> macFieldVal :: String -> [[Ast]] -> [Ast];
+> macFieldVal _ [] = [];
+> macFieldVal s ([] : t) = macFieldVal s t;
+> macFieldVal s (h : t) = bool [] (h ++ [Single $ newToken { val = s }])
+>  (any (valIs "=") h) ++ macFieldVal s t;
+
+\: Calculation.
+
+> type Calc = Either Integer String;
+
+> calcAst :: [Calc] -> [Ast] -> [Ast];
+> calcAst _ [] = [];
+
+Integer literals.
+
+> calcAst c (Single (Token { tag = IntLit, val = v }) : t) =
+>  calcAst (Left (read v) : c) t;
+
+String literals.
+
+> calcAst c (Single (Token { tag = StringLit, val = v }) : t) =
+>  calcAst (Right (read v) : c) t;
+
+{\tt+} Addition or string concatenation.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "+" }) : t) =
+>  calcAst (Left (y + x) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "+" }) : t) =
+>  calcAst (Right (y ++ x) : z) t;
+
+{\tt-} Subtraction or remove characters from string.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "-" }) : t) =
+>  calcAst (Left (y - x) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "-" }) : t) =
+>  calcAst (Right (filter (flip notElem x) y) : z) t;
+
+{\tt*} Multiplication, string repetition, or keep only certain characters
+in the string.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "*" }) : t) =
+>  calcAst (Left (y * x) : z) t;
+> calcAst (Left x : Right y : z) (Single (Token { val = "*" }) : t) =
+>  calcAst (Right (join $ genericReplicate x y) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "*" }) : t) =
+>  calcAst (Right (filter (flip elem x) y) : z) t;
+
+{\tt/} Division.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "/" }) : t) =
+>  calcAst (Left (div y x) : z) t;
+
+{\tt\%} Modulo.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "%" }) : t) =
+>  calcAst (Left (mod y x) : z) t;
+
+{\tt<} Less.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "<" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y < x)) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "<" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y < x)) : z) t;
+
+{\tt>} Greater.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = ">" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y > x)) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = ">" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y > x)) : z) t;
+
+{\tt=} Equal.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "=" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y == x)) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "=" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y == x)) : z) t;
+
+{\tt<>} Unequal.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "<>" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y /= x)) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "<>" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y /= x)) : z) t;
+
+{\tt<=} Less or equal.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = "<=" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y <= x)) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = "<=" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y <= x)) : z) t;
+
+{\tt>=} Greater or equal.
+
+> calcAst (Left x : Left y : z) (Single (Token { val = ">=" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y >= x)) : z) t;
+> calcAst (Right x : Right y : z) (Single (Token { val = ">=" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum (y >= x)) : z) t;
+
+{\tt;} Drop from stack.
+
+> calcAst (_ : y) (Single (Token { val = ";" }) : t) =
+>  calcAst y t;
+
+{\tt\$} Swap values on stack.
+
+> calcAst (x : y : z) (Single (Token { val = "$" }) : t) =
+>  calcAst (y : x : z) t;
+
+{\tt@} Convert to showable form.
+
+> calcAst (x : y) (Single (Token { val = "@" }) : t) =
+>  calcAst (Right (either show show x) : y) t;
+
+{\tt?} Convert ASCII value to string.
+
+> calcAst (Left x : y) (Single (Token { val = "?" }) : t) =
+>  calcAst (Right [toEnum $ fromInteger x] : y) t;
+
+{\tt,} Enter tokens into program.
+
+> calcAst (Left x : y) (Single (Token { val = "," }) : t) =
+>  Single (newToken { tag = IntLit, val = show x }) : calcAst y t;
+> calcAst (Right x : y) (Single (Token { val = "," }) : t) =
+>  either (error . show) id (parseBy mySpec "<macro>" x) ++ calcAst y t;
+
+{\tt:} Take first character of string.
+
+> calcAst (Right [] : z) (Single (Token { val = ":" }) : t) =
+>  calcAst (Left 0 : Right [] : z) t;
+> calcAst (Right (x : y) : z) (Single (Token { val = ":" }) : t) =
+>  calcAst (Left (toInteger $ fromEnum x) : Right y : z) t;
+
+{\tt()} Conditional block.
+
+> calcAst (Left x : y) (Block { item = Token { val = "(" },
+>  body = b } : t) | x /= 0 = calcAst y (b ++ t);
+> calcAst (Right (_ : _) : y) (Block { item = Token { val = "(" },
+>  body = b } : t) = calcAst y (b ++ t);
+> calcAst (_ : y) (Block { item = Token { val = "(" }, body = b } : t) =
+>  calcAst y t;
+
+{\tt[]} Looping block.
+
+> calcAst c (h @ Block { item = Token { val = "[" }, body = b } : t) =
+>  calcAst c (h { item = newToken { val = "(" }, body = b ++ [h] } : t);
+
+{\tt\char`\{\char`\}} For/foreach loop.
+
+> calcAst (Left x : y) (Block { item = Token { val = "{" },
+>  body = b } : t) = calcAst y (join (genericReplicate x b) ++ t);
+> calcAst (Right x : y) (Block { item = Token { val = "{" },
+>  body = b } : t) = calcAst y ((x >>= \z -> Single
+>  (newToken { tag = IntLit, val = show $ fromEnum z }) : b) ++ t);
+
+Everything else is ignored.
+
+> calcAst c (_ : t) = calcAst c t;
+
+\: Comma Elimination. In blocks with curly braces and with square
+brackets, consecutive commas with nothing in between can be eliminated and
+final comma removed. So that you can write a comma after every line in a
+field list of a record or a list data. (For parentheses, it should not
+eliminate commas in this way since that is type for tuples)
+
+First parameter will be true if inside a block to eliminate commas.
+
+This also changes {\tt(\#} and {\tt\#)} to {\tt(} and {\tt)} so that you
+can use those to override blocking and macros.
+
+> commaElim :: Bool -> [Ast] -> [Ast];
+> commaElim _ [] = [];
+> commaElim True [Single (Token { val = "," })] = [];
+> commaElim True (x @ (Single (Token { val = "," })) :
+>  Single (Token { val = "," }) : t) = commaElim True (x : t);
+> commaElim b (x @ Block { item = Token { val = v }, body = y } : t) =
+>  x { body = commaElim (v == "[" || v == "{") y } : commaElim b t;
+> commaElim b (Single (x @ Token { val = "(#" }) : t) =
+>  Single (x { val = "(" }) : commaElim b t;
+> commaElim b (Single (x @ Token { val = "#)" }) : t) =
+>  Single (x { val = ")" }) : commaElim b t;
+> commaElim b (h : t) = h : commaElim b t;
+
+\: Output. The existing {\tt dump} function seems long to me, and doesn't
+seems to work well anyways, so I wrote my own. (This one does not support
+layout, though. Actually, this entire program does not support layout.)
+
+> writeTokens :: Handle -> Loc -> [Token] -> IO ();
+> writeTokens _ _ [] = pure ();
+> writeTokens h l (x : t) = writeUpdateLoc h l (loc x) >>
+>  hPutStr h (val x) >> writeTokens h (advance (loc x) (val x)) t;
+
+> writeUpdateLoc :: Handle -> Loc -> Loc -> IO ();
+> writeUpdateLoc h x y | isBogus y = unless (isBogus x) $ hPutChar h '\n';
+> writeUpdateLoc h x y | (file x, line x) == (file y, line y)
+>  && col x <= col y = hPutStr h $ replicate (col y - col x) ' ';
+> writeUpdateLoc h x y | (file x, line x + 1) == (file y, line y) =
+>  hPutStr h $ '\n' : replicate (col y - 1) ' ';
+> writeUpdateLoc h x y = hPutStr h $ "\n# " ++ show (line y) ++ " "
+>  ++ show (file y) ++ "\n" ++ replicate (col y - 1) ' ';
+
+> myFileDump :: FilePath -> [Ast] -> IO ();
+> myFileDump p a = withFile p WriteMode $ \h -> writeTokens h bogus $
+>  flattenList a [];
+
+\: Main Program. This is the main program as required by the {\tt-pgmF}
+option for GHC.
+
+> main :: IO ();
+> main = getArgs >>= \(orig : cur : outf : _) -> fileLoad mySpec orig cur
+>  >>= either (fail . show) (runIncludeFiles >=> myFileDump outf
+>  . commaElim False . macroProc . (,) M.empty . reorderProg . fixAst);
+
+There are a few for testing.
+
+> testFile :: FilePath -> IO [Ast];
+> testFile fp = fileLoad mySpec fp fp >>= either (fail . show)
+>  (runIncludeFiles >=> return . commaElim False . macroProc
+>  . (,) M.empty . reorderProg . fixAst);
+
+% End of document (final "}" is suppressed from printout)
+\toks0={{
+
+> } -- }\bye
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple
+main = defaultMain
+ hampp.cabal view
@@ -0,0 +1,39 @@+Name:                hampp
+-- The package version. See the Haskell package versioning policy
+-- http://www.haskell.org/haskellwiki/Package_versioning_policy for
+-- standards guiding when and how versions should be incremented.
+Version:             0.2
+Synopsis:            Haskell macro preprocessor
+License:             PublicDomain
+Category:            Preprocessor
+Build-type:          Simple
+Cabal-version:       >=1.6
+Tested-with:         GHC == 7.0.3
+
+X-Printout-Mode:     PlainTeX
+X-Printout-Main:     Main.lhs
+X-Printout-Require:  birdstyle.tex
+
+Executable hampp
+  Main-is:             Main.lhs
+  Build-depends:
+    base == 4.*,
+    bytestring >= 0.9.1.10 && < 0.10,
+    containers >= 0.4.0.0 && < 0.5,
+    directory >= 1.1 && < 1.2,
+    filepath >= 1.2.0.0 && < 1.4,
+    preprocessor-tools >= 0.1.2 && < 0.2
+
+---- Features:
+-- Named chunk rearrangement (as in WEB)
+-- Built-in macros
+-- User defined macros
+-- Binary and ASCII integer literals
+-- Include files (Haskell and literate Haskell)
+-- Remove commas at end of {a,b,c,} and [a,b,c,]
+
+---- To-do:
+-- Other file formats (C header, CSV, XML, etc)
+-- Layout mode
+-- Pool strings
+-- Any other feature that you might want; please ask