content: {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE OverloadedStrings #-}
  
  {-
     **************************************************************
      BNF Converter Module
  
      Description   : This module generates the C++ Pretty Printer.
                      It also generates the "show" method for
                      printing an abstract syntax tree.
  
                      The generated files use the Visitor design pattern.
  
      Author        : Michael Pellauer
      Created       : 10 August, 2003
      Modified      : 3 September, 2003
                      * Added resizable buffers
  
     **************************************************************
  -}
  
  module BNFC.Backend.CPP.PrettyPrinter (cf2CPPPrinter, prRender) where
  
  import Prelude hiding ((<>))
  
  import Data.Bifunctor (second)
  import Data.Char  (toLower)
  
  import BNFC.CF
  import BNFC.Utils
  import BNFC.Backend.Common
  import BNFC.Backend.Common.NamedVariables
  import BNFC.Backend.Common.StrUtils (renderCharOrString)
  import BNFC.Backend.CPP.STL.STLUtils
  import BNFC.PrettyPrint
  
  --Produces (.H file, .C file)
  cf2CPPPrinter :: Bool -> Maybe String -> CF -> (String, String)
  cf2CPPPrinter useStl inPackage cf =
      (mkHFile useStl inPackage cf groups, mkCFile useStl inPackage cf groups)
   where
      groups = when useStl (positionRules cf)  -- CPP/NoSTL treats position tokens as just tokens
            ++ fixCoercions (ruleGroupsInternals cf)
  
  positionRules :: CF -> [(Cat,[Rule])]
  positionRules cf =
    [ (TokenCat cat, [ Rule (noPosition cat) (noPosition $ TokenCat cat) (map (Left . TokenCat) [catString, catInteger]) Parsable ])
    | cat <- filter (isPositionCat cf) $ map fst (tokenPragmas cf)
    ]
  
  {- **** Header (.H) File Methods **** -}
  
  --An extremely large function to make the Header File
  mkHFile :: Bool -> Maybe String -> CF -> [(Cat,[Rule])] -> String
  mkHFile useStl inPackage cf groups = unlines
    [ printHeader
    , content
    , classFooter
    , showHeader
    , content
    , classFooter
    , footer
    ]
    where
    printHeader = unlines
     [
      "#ifndef " ++ hdef,
      "#define " ++ hdef,
      "",
      "#include \"Absyn.H\"",
      "#include <stdio.h>",
      "#include <stddef.h>",
      "#include <string.h>",
      "#include <stdlib.h>",
      "",
      nsStart inPackage,
      "/* Certain applications may improve performance by changing the buffer size */",
      "#define " ++ nsDefine inPackage "BUFFER_INITIAL" ++ " 2000",
      "/* You may wish to change _L_PAREN or _R_PAREN */",
      "#define " ++ nsDefine inPackage "_L_PAREN" ++ " '('",
      "#define " ++ nsDefine inPackage "_R_PAREN" ++ " ')'",
      "",
      "class PrintAbsyn : public Visitor",
      "{",
      " protected:",
      "  int _n_, _i_;",
      "  /* The following are simple heuristics for rendering terminals */",
      "  /* You may wish to change them */",
      "  void render(Char c);",
      if useStl then "  void render(String s);" else "",
      "  void render(const char *s);",
      "  void indent(void);",
      "  void backup(void);",
      "  void onEmptyLine(void);",
      "  void removeTrailingSpaces(void);",
      "  void removeTrailingWhitespace(void);",
      " public:",
      "  PrintAbsyn(void);",
      "  ~PrintAbsyn(void);",
      "  char *print(Visitable *v);"
     ]
    hdef = nsDefine inPackage "PRINTER_HEADER"
    content = concatMap (prDataH useStl) groups
    classFooter = unlines $
     [
      "  void visitInteger(Integer i);",
      "  void visitDouble(Double d);",
      "  void visitChar(Char c);",
      "  void visitString(String s);",
      "  void visitIdent(String s);"
     ] ++ ["  void visit" ++ t ++ "(String s);" | t <- tokenNames cf] ++
     [
      " protected:",
      "  char *buf_;",
      "  size_t cur_, buf_size;",
      "",
      "  void inline bufEscape(const char c)",
      "  {",
      "    switch(c)",
      "    {",
      "      case '\\f': bufAppend(\"\\\\f\" ); break;",
      "      case '\\n': bufAppend(\"\\\\n\" ); break;",
      "      case '\\r': bufAppend(\"\\\\r\" ); break;",
      "      case '\\t': bufAppend(\"\\\\t\" ); break;",
      "      case '\\v': bufAppend(\"\\\\v\" ); break;",
      "      case '\\\\': bufAppend(\"\\\\\\\\\"); break;",
      "      case '\\'': bufAppend(\"\\\\'\" ); break;",
      "      case '\\\"': bufAppend(\"\\\\\\\"\"); break;",
      "      default:   bufAppend(c);",
      "    }",
      "  }",
      "",
      "  void inline bufEscape(const char *s)",
      "  {",
      "    if (s) while (*s) bufEscape(*s++);",
      "  }",
      "",
      if useStl then render (nest 2 $ bufAppendString "bufEscape") else "",
      "  void inline bufAppend(const char *s)",
      "  {",
      "    size_t end = cur_ + strlen(s);",
      "    if (end >= buf_size) {",
      "      do buf_size *= 2; /* Double the buffer size */",
      "      while (end >= buf_size);",
      "      resizeBuffer();",
      "    }",
      "    strcpy(&buf_[cur_], s);",
      "    cur_ = end;",
      "  }",
      "",
      "  void inline bufAppend(const char c)",
      "  {",
      "    if (cur_ + 1 >= buf_size)",
      "    {",
      "      buf_size *= 2; /* Double the buffer size */",
      "      resizeBuffer();",
      "    }",
      "    buf_[cur_] = c;",
      "    buf_[++cur_] = 0;",
      "  }",
      "",
      if useStl then render (nest 2 $ bufAppendString "bufAppend") else "",
      "  void inline bufReset(void)",
      "  {",
      "    if (buf_) delete[] buf_;",
      "    buf_size = " ++ nsDefine inPackage "BUFFER_INITIAL" ++ ";",
      "    buf_ = new char[buf_size];",
      "    memset(buf_, 0, buf_size);",
      "    cur_ = 0;",
      "  }",
      "",
      "  void inline resizeBuffer(void)",
      "  {",
      "    char *temp = new char[buf_size];",
      "    if (buf_)",
      "    {",
      "      strcpy(temp, buf_);",
      "      delete[] buf_;",
      "    }",
      "    buf_ = temp;",
      "  }",
      "};",
      ""
     ]
    bufAppendString :: Doc -> Doc
    bufAppendString f =
        "void inline" <+> f <> "(String str)"
        $$ codeblock 2
            [ "const char *s = str.c_str();"
            , f <> "(s);"
            ]
    showHeader = unlines
     [
      "",
      "class ShowAbsyn : public Visitor",
      "{",
      " public:",
      "  ShowAbsyn(void);",
      "  ~ShowAbsyn(void);",
      "  char *show(Visitable *v);"
     ]
    footer = unlines
     [
      nsEnd inPackage,
      "",
      "#endif"
     ]
  
  --Prints all the required method names and their parameters.
  prDataH :: Bool -> (Cat, [Rule]) -> String
  prDataH useSTL (cat, rules)
   | isList cat = unlines $ concat
       [ [ concat [ "  void visit", cl, "(", cl, " *p);"          ] ]
       , when useSTL
         [ concat [ "  void iter", cl, "(", itty, " i, ", itty, " j);" ] ]
       ]
   | otherwise  = abstract ++ concatMap prRuleH rules
   where
     cl       = identCat (normCat cat)
     itty     = concat [ cl, "::", "const_iterator" ]
     abstract = case lookupRule (noPosition $ catToStr cat) rules of
      Just _ -> ""
      Nothing ->  "  void visit" ++ cl ++ "(" ++ cl ++ " *p); /* abstract class */\n"
  
  --Prints all the methods to visit a rule.
  prRuleH :: IsFun f => Rul f -> String
  prRuleH (Rule fun _ _ _) | isProperLabel fun = concat
    ["  void visit", funName fun, "(", funName fun, " *p);\n"]
  prRuleH _ = ""
  
  {- **** Implementation (.C) File Methods **** -}
  
  --This makes the .C file by a similar method.
  mkCFile :: Bool -> Maybe String -> CF -> [(Cat,[Rule])] -> String
  mkCFile useStl inPackage cf groups = concat
     [
      header,
      nsStart inPackage ++ "\n",
      prRender useStl,
      printEntries,
      concatMap (prPrintData useStl inPackage cf) groups,
      printBasics,
      printTokens,
      showEntries,
      concatMap (prShowData useStl) groups,
      showBasics,
      showTokens,
      nsEnd inPackage ++ "\n"
     ]
    where
      header = unlines
       [
        "/*** Pretty Printer and Abstract Syntax Viewer ***/",
        "",
        "#include <string>",
        "#include \"Printer.H\"",
        "#define INDENT_WIDTH 2",
        ""
       ]
      printEntries = unlines
       [
        "PrintAbsyn::PrintAbsyn(void)",
        "{",
        "  _i_ = 0; _n_ = 0;",
        "  buf_ = 0;",
        "  bufReset();",
        "}",
        "",
        "PrintAbsyn::~PrintAbsyn(void)",
        "{",
        "}",
        "",
        "char *PrintAbsyn::print(Visitable *v)",
        "{",
        "  _i_ = 0; _n_ = 0;",
        "  bufReset();",
        "  v->accept(this);",
        "  return buf_;",
        "}",
        ""
       ]
      showEntries = unlines
       [
        "ShowAbsyn::ShowAbsyn(void)",
        "{",
        "  buf_ = 0;",
        "  bufReset();",
        "}",
        "",
        "ShowAbsyn::~ShowAbsyn(void)",
        "{",
        "}",
        "",
        "char *ShowAbsyn::show(Visitable *v)",
        "{",
        "  bufReset();",
        "  v->accept(this);",
        "  return buf_;",
        "}",
        ""
       ]
      printBasics = unlines
       [
        "void PrintAbsyn::visitInteger(Integer i)",
        "{",
        "  char tmp[20];",
        "  sprintf(tmp, \"%d\", i);",
        "  render(tmp);",
        "}",
        "",
        "void PrintAbsyn::visitDouble(Double d)",
        "{",
        "  char tmp[24];",
        "  sprintf(tmp, \"%.15g\", d);",
        "  render(tmp);",
        "}",
        "",
        "void PrintAbsyn::visitChar(Char c)",
        "{",
        "  bufAppend('\\'');",
        "  bufEscape(c);",
        "  bufAppend('\\'');",
        "  bufAppend(' ');",
        "}",
        "",
        "void PrintAbsyn::visitString(String s)",
        "{",
        "  bufAppend('\\\"');",
        "  bufEscape(s);",
        "  bufAppend('\\\"');",
        "  bufAppend(' ');",
        "}",
        "",
        "void PrintAbsyn::visitIdent(String s)",
        "{",
        "  render(s);",
        "}",
        ""
       ]
  
      printTokens = unlines
       [unlines [
        "void PrintAbsyn::visit" ++ t ++ "(String s)",
        "{",
        "  render(s);",
        "}",
        ""
        ] | t <- tokenNames cf
       ]
  
      showBasics = unlines
       [
        "void ShowAbsyn::visitInteger(Integer i)",
        "{",
        "  char tmp[20];",
        "  sprintf(tmp, \"%d\", i);",
        "  bufAppend(tmp);",
        "}",
        "void ShowAbsyn::visitDouble(Double d)",
        "{",
        "  char tmp[24];",
        "  sprintf(tmp, \"%.15g\", d);",
        "  bufAppend(tmp);",
        "}",
        "void ShowAbsyn::visitChar(Char c)",
        "{",
        "  bufAppend('\\'');",
        "  bufEscape(c);",
        "  bufAppend('\\'');",
        "}",
        "void ShowAbsyn::visitString(String s)",
        "{",
        "  bufAppend('\\\"');",
        "  bufEscape(s);",
        "  bufAppend('\\\"');",
        "}",
        "void ShowAbsyn::visitIdent(String s)",
        "{",
        "  bufAppend('\\\"');",
        "  bufAppend(s);",
        "  bufAppend('\\\"');",
        "}",
        ""
       ]
  
      showTokens = unlines
       [unlines [
        "void ShowAbsyn::visit" ++ t ++ "(String s)",
        "{",
        "  bufAppend('\\\"');",
        "  bufAppend(s);",
        "  bufAppend('\\\"');",
        "}",
        ""
        ] | t <- tokenNames cf
       ]
  
  
  {- **** Pretty Printer Methods **** -}
  
  -- | Generates methods for the Pretty Printer.
  prPrintData :: Bool -> Maybe String -> CF -> (Cat, [Rule]) -> String
  prPrintData True {- use STL -} _ _ (cat@(ListCat _), rules) =
      render $ genPrintVisitorList (cat, rules)
  prPrintData False {- use STL -} _ _ (cat@(ListCat _), rules) =
      genPrintVisitorListNoStl (cat, rules)
  -- Not a list :
  prPrintData _ _inPackage cf (TokenCat cat, _rules) | isPositionCat cf cat = unlines $
    -- a position token
    [ "void PrintAbsyn::visit" ++ cat ++ "(" ++ cat ++ " *p)"
    , "{"
    , "  visitIdent(p->string_);"
    , "}"
    , ""
    ]
  prPrintData _ inPackage _cf (cat, rules) = -- Not a list
      abstract ++ concatMap (prPrintRule inPackage) rules
    where
    cl = identCat (normCat cat)
    abstract = case lookupRule (noPosition $ catToStr cat) rules of
      Just _ -> ""
      Nothing ->  "void PrintAbsyn::visit" ++ cl ++ "(" ++ cl +++ "*p) {} //abstract class\n\n"
  
  -- | Generate pretty printer visitor for a list category (STL version).
  --
  genPrintVisitorList :: (Cat, [Rule]) -> Doc
  genPrintVisitorList (cat@(ListCat _), rules) = vcat
    [ "void PrintAbsyn::visit" <> lty <> parens (lty <+> "*" <> vname)
    , codeblock 2
      [ "iter" <> lty <> parens (vname <> "->begin()" <> comma <+> vname <> "->end()") <> semi ]
    , ""
    , "void PrintAbsyn::iter" <> lty <> parens (itty <+> "i" <> comma <+> itty <+> "j")
    , codeblock 2 $ concat
      [ if null docs0 then
        [ "if (i == j) return;" ]
        else
        [ "if (i == j)"
        , "{ /* nil */"
        , nest 2 $ vcat docs0
        , "}"
        , "else"
        ]
      , unless (null docs1)
        [ "if (i == j-1)"
        , "{ /* last */"
        , nest 2 $ vcat docs1
        , "}"
        , "else"
        ]
      , [ "{ /* cons */"
        ,  nest 2 $ vcat docs2
        , "}"
        ]
      ]
    , ""
    , ""
    ]
    where
    cl        = identCat (normCat cat)
    lty       = text cl                   -- List type
    itty      = lty <> "::const_iterator" -- Iterator type
    vname     = text $ map toLower cl
    prules    = sortRulesByPrecedence rules
    swRules f = switchByPrecedence "_i_" $
                  map (second $ sep . prListRule_) $
                    uniqOn fst $ filter f prules
                    -- Discard duplicates, can only handle one rule per precedence.
    docs0     = swRules isNilFun
    docs1     = swRules isOneFun
    docs2     = swRules isConsFun
  
  genPrintVisitorList _ = error "genPrintVisitorList expects a ListCat"
  
  -- | Only render the rhs (items) of a list rule.
  
  prListRule_ :: IsFun a => Rul a -> [Doc]
  prListRule_ (Rule _ _ items _) = for items $ \case
    Right t       -> "render(" <> text (snd (renderCharOrString t)) <> ");"
    Left c
      | Just{} <- maybeTokenCat c
                  -> "visit" <> dat <> "(*i);"
      | isList c  -> "iter" <> dat <> "(i+1, j);"
      | otherwise -> "(*i)->accept(this);"
      where
      dat = text $ identCat $ normCat c
  
  -- This is the only part of the pretty printer that differs significantly
  -- between the versions with and without STL.
  -- The present version has been adapted from CFtoCPrinter.
  genPrintVisitorListNoStl :: (Cat, [Rule]) -> String
  genPrintVisitorListNoStl (cat@(ListCat _), rules) = unlines $ concat
    [ [ "void PrintAbsyn::visit" ++ cl ++ "("++ cl ++ " *" ++ vname ++ ")"
      , "{"
        , "  if (" ++ vname +++ "== 0)"
        , "  { /* nil */"
        ]
      , unlessNull (swRules isNilFun) $ \ docs ->
        [ render $ nest 4 $ vcat docs ]
      , [ "  }" ]
      , unlessNull (swRules isOneFun) $ \ docs ->
        [ "  else if (" ++ pre ++ vname ++ "_ == 0)"
        , "  { /* last */"
        , render $ nest 4 $ vcat docs
        , "  }"
        ]
      , unlessNull (swRules isConsFun) $ \ docs ->
        [ "  else"
        , "  { /* cons */"
        , render $ nest 4 $ vcat docs
        , "  }"
        ]
      , [ "}"
        , ""
        ]
      ]
    where
    cl          = identCat (normCat cat)
    vname       = map toLower cl
    pre         = vname ++ "->"
    prules      = sortRulesByPrecedence rules
    swRules f   = switchByPrecedence "_i_" $
                    map (second $ sep . map text . prPrintRule_ pre) $
                      uniqOn fst $ filter f prules
                      -- Discard duplicates, can only handle one rule per precedence.
  genPrintVisitorListNoStl _ = error "genPrintVisitorListNoStl expects a ListCat"
  
  --Pretty Printer methods for a rule.
  prPrintRule :: Maybe String -> Rule -> String
  prPrintRule inPackage r@(Rule fun _ _ _) | isProperLabel fun = unlines $ concat
    [ [ "void PrintAbsyn::visit" ++ funName fun ++ "(" ++ funName fun +++ "*" ++ fnm ++ ")"
      , "{"
      , "  int oldi = _i_;"
      , parenCode "_L_PAREN"
      , ""
      ]
    , prPrintRule_ (fnm ++ "->") r
    , [ ""
      , parenCode "_R_PAREN"
      , "  _i_ = oldi;"
      , "}"
      , ""
      ]
    ]
    where
    p = precRule r
    parenCode x = "  if (oldi > " ++ show p ++ ") render(" ++ nsDefine inPackage x ++ ");"
    fnm = "p" --old names could cause conflicts
  prPrintRule _ _ = ""
  
  prPrintRule_ :: IsFun a => String -> Rul a -> [String]
  prPrintRule_ pre (Rule _ _ items _) = map (prPrintItem pre) $ numVars items
  
  --This goes on to recurse to the instance variables.
  prPrintItem :: String -> Either (Cat, Doc) String -> String
  prPrintItem _   (Right t) = "  render(" ++ snd (renderCharOrString t) ++ ");"
  prPrintItem pre (Left (c, nt))
    | Just t <- maybeTokenCat c
                = "  visit" ++ t   ++ "(" ++ pre ++ s ++ ");"
    | isList c  = "  " ++ setI (precCat c) ++
                    "visit" ++ elt ++ "(" ++ pre ++ s ++ ");"
    | otherwise = "  " ++ setI (precCat c) ++ pre ++ s ++ "->accept(this);"
    where
    s   = render nt
    elt = identCat $ normCat c
  
  {- **** Abstract Syntax Tree Printer **** -}
  
  --This prints the functions for Abstract Syntax tree printing.
  prShowData :: Bool -> (Cat, [Rule]) -> String
  prShowData True (cat@(ListCat c), _) = unlines
   [
    "void ShowAbsyn::visit" ++ cl ++ "("++ cl ++ " *" ++ vname ++ ")",
    "{",
    "  for ("++ cl ++"::const_iterator i = " ++
         vname++"->begin() ; i != " ++vname ++"->end() ; ++i)",
    "  {",
    if isTokenCat c
      then "    visit" ++ baseName cl ++ "(*i) ;"
      else "    (*i)->accept(this);",
    "    if (i != " ++ vname ++ "->end() - 1) bufAppend(\", \");",
    "  }",
    "}",
    ""
   ]
    where
      cl = identCat (normCat cat)
      vname = map toLower cl
  prShowData False (cat@(ListCat c), _) =
   unlines
   [
    "void ShowAbsyn::visit" ++ cl ++ "("++ cl ++ " *" ++ vname ++ ")",
    "{",
    "  while(" ++ vname ++ "!= 0)",
    "  {",
    "    if (" ++ vname ++ "->" ++ vname ++ "_)",
    "    {",
    visitMember,
    "      bufAppend(\", \");",
    "      " ++ vname +++ "=" +++ vname ++ "->" ++ vname ++ "_;",
    "    }",
    "    else",
    "    {",
    visitMember,
    "      " ++ vname ++ " = 0;",
    "    }",
    "  }",
    "}",
    ""
   ]
    where
      cl = identCat (normCat cat)
      ecl = identCat (normCatOfList cat)
      vname = map toLower cl
      member = map toLower ecl ++ "_"
      visitMember
        | Just t <- maybeTokenCat c =
            "      visit" ++ t ++ "(" ++ vname ++ "->" ++ member ++ ");"
        | otherwise =
            "      " ++ vname ++ "->" ++ member ++ "->accept(this);"
  prShowData _ (cat, rules) =  --Not a list:
    abstract ++ concatMap prShowRule rules
    where
      cl = identCat (normCat cat)
      abstract = case lookupRule (noPosition $ catToStr cat) rules of
        Just _ -> ""
        Nothing ->  "void ShowAbsyn::visit" ++ cl ++ "(" ++ cl ++ " *p) {} //abstract class\n\n"
  
  --This prints all the methods for Abstract Syntax tree rules.
  prShowRule :: IsFun f => Rul f -> String
  prShowRule (Rule f _ cats _) | isProperLabel f = concat
    [
     "void ShowAbsyn::visit" ++ fun ++ "(" ++ fun +++ "*" ++ fnm ++ ")\n",
     "{\n",
     lparen,
     "  bufAppend(\"" ++ fun ++ "\");\n",
     optspace,
     cats',
     rparen,
     "}\n"
    ]
     where
      fun = funName f
      (optspace, lparen, rparen, cats')
        | null [ () | Left _ <- cats ]  -- @all isRight cats@, but Data.Either.isRight requires base >= 4.7
                    = ("", "", "", "")
        | otherwise = ("  bufAppend(' ');\n", "  bufAppend('(');\n","  bufAppend(')');\n"
                      , concat (insertSpaces (map (prShowCat fnm) (numVars cats))))
      insertSpaces [] = []
      insertSpaces (x:[]) = [x]
      insertSpaces (x:xs) = if x == ""
        then insertSpaces xs
        else x : "  bufAppend(' ');\n" : insertSpaces xs
      fnm = "p" --other names could cause conflicts
  prShowRule _ = ""
  
  -- This recurses to the instance variables of a class.
  prShowCat :: String -> Either (Cat, Doc) String -> String
  prShowCat _   (Right _) = ""
  prShowCat fnm (Left (cat, nt))
    | Just t <- maybeTokenCat cat =
        unlines
          [ "  visit" ++ t ++ "(" ++ fnm ++ "->" ++ s ++ ");"
          ]
    | catToStr (normCat $ strToCat s) /= s =
        unlines
          [ accept
          ]
    | otherwise =
        unlines
          [ "  bufAppend('[');"
          , "  if (" ++ fnm ++ "->" ++ s ++ ")" ++ accept
          , "  bufAppend(']');"
          ]
    where
    s = render nt
    accept = "  " ++ fnm ++ "->" ++ s ++ "->accept(this);"
  
  {- **** Helper Functions Section **** -}
  
  -- from ListIdent to Ident
  baseName :: [a] -> [a]
  baseName = drop 4
  
  --Just sets the coercion level for parentheses in the Pretty Printer.
  setI :: Integer -> String
  setI n = "_i_ = " ++ show n ++ "; "
  
  --An extremely simple renderer for terminals.
  prRender :: Bool -> String
  prRender useStl = unlines $ concat
    [ [
        "//You may wish to change render",
        "void PrintAbsyn::render(Char c)",
        "{",
        "  if (c == '{')",
        "  {",
        "     onEmptyLine();",
        "     bufAppend(c);",
        "     _n_ = _n_ + INDENT_WIDTH;",
        "     bufAppend('\\n');",
        "     indent();",
        "  }",
        "  else if (c == '(' || c == '[')",
        "     bufAppend(c);",
        "  else if (c == ')' || c == ']')",
        "  {",
        "     removeTrailingWhitespace();",
        "     bufAppend(c);",
        "     bufAppend(' ');",
        "  }",
        "  else if (c == '}')",
        "  {",
        "     _n_ = _n_ - INDENT_WIDTH;",
        "     onEmptyLine();",
        "     bufAppend(c);",
        "     bufAppend('\\n\');",
        "     indent();",
        "  }",
        "  else if (c == ',')",
        "  {",
        "     removeTrailingWhitespace();",
        "     bufAppend(c);",
        "     bufAppend(' ');",
        "  }",
        "  else if (c == ';')",
        "  {",
        "     removeTrailingWhitespace();",
        "     bufAppend(c);",
        "     bufAppend('\\n');",
        "     indent();",
        "  }",
        "  else if (c == ' ') bufAppend(c);",
        "  else if (c == 0) return;",
        "  else",
        "  {",
        "     bufAppend(c);",
        "     bufAppend(' ');",
        "  }",
        "}",
        ""
      ]
    , when useStl
      [ render $ vcat
          [ "void PrintAbsyn::render(String s)"
          , codeblock 2
              [ "render(s.c_str());"
              ]
          , ""
          ]
      ]
    , [ "bool allIsSpace(const char *s)"
      , "{"
      , "  char c;"
      , "  while ((c = *s++))"
      , "    if (! isspace(c)) return false;"
      , "  return true;"
      , "}"
      , ""
      ]
    , [ "void PrintAbsyn::render(const char *s)"
      , "{"
      , "  if (*s) /* C string not empty */"
      , "  {"
      , "    if (allIsSpace(s)) {"
      , "      backup();"
      , "      bufAppend(s);"
      , "    } else {"
      , "      bufAppend(s);"
      , "      bufAppend(' ');"
      , "    }"
      , "  }"
      , "}"
      , ""
      , "void PrintAbsyn::indent()"
      , "{"
      , "  int n = _n_;"
      , "  while (--n >= 0)"
      , "    bufAppend(' ');"
      , "}"
      , ""
      , "void PrintAbsyn::backup()"
      , "{"
      , "  if (cur_ && buf_[cur_ - 1] == ' ')"
      , "    buf_[--cur_] = 0;"
      , "}"
      , ""
      , "void PrintAbsyn::removeTrailingSpaces()"
      , "{"
      , "  while (cur_ && buf_[cur_ - 1] == ' ') --cur_;"
      , "  buf_[cur_] = 0;"
      , "}"
      , ""
      , "void PrintAbsyn::removeTrailingWhitespace()"
      , "{"
      , "  while (cur_ && (buf_[cur_ - 1] == ' ' || buf_[cur_ - 1] == '\\n')) --cur_;"
      , "  buf_[cur_] = 0;"
      , "}"
      , ""
      , "void PrintAbsyn::onEmptyLine()"
      , "{"
      , "  removeTrailingSpaces();"
      , "  if (cur_ && buf_[cur_ - 1 ] != '\\n') bufAppend('\\n');"
      , "  indent();"
      , "}"
      , ""
      ]
    ]
  