{-
    BNF Converter: C++ Main file
    Copyright (C) 2004  Author:  Markus Forsberg, Michael Pellauer
    Copyright (C) 2020  Andreas Abel

    Modified from CPPTop to BNFC.Backend.CPP.STL 2006 by Aarne Ranta.

-}

module BNFC.Backend.CPP.STL (makeCppStl, mkEntryFuncs) where

import Data.Foldable (toList)

import BNFC.Utils
import BNFC.CF
import BNFC.Options
import BNFC.Backend.Base
import BNFC.Backend.C            ( bufferH, bufferC, comment, testfileHeader )
import BNFC.Backend.C.CFtoBisonC ( cf2Bison )
import BNFC.Backend.C.CFtoFlexC  ( cf2flex, ParserMode(..) )
import BNFC.Backend.CPP.Common   ( commentWithEmacsModeHint )
import BNFC.Backend.CPP.Makefile
import BNFC.Backend.CPP.STL.CFtoSTLAbs
import BNFC.Backend.CPP.STL.CFtoCVisitSkelSTL
import BNFC.Backend.CPP.PrettyPrinter
import BNFC.Backend.CPP.STL.STLUtils
import qualified BNFC.Backend.Common.Makefile as Makefile

makeCppStl :: SharedOptions -> CF -> MkFiles ()
makeCppStl opts cf = do
    let (hfile, cfile) = cf2CPPAbs (linenumbers opts) (inPackage opts) name cf
    mkCppFile "Absyn.H" hfile
    mkCppFile "Absyn.C" cfile
    mkCppFile "Buffer.H" bufferH
    mkCppFile "Buffer.C" $ bufferC "Buffer.H"
    let (flex, env) = cf2flex parserMode cf
    mkCppFileWithHint (name ++ ".l") flex
    mkCppFileWithHint (name ++ ".y") $ cf2Bison (linenumbers opts) parserMode cf env
    mkCppFile "Parser.H" $
      mkHeaderFile (inPackage opts) (toList $ allEntryPoints cf)
    mkCppFile "ParserError.H" $ printParseErrHeader (inPackage opts)
    let (skelH, skelC) = cf2CVisitSkel True (inPackage opts) cf
    mkCppFile "Skeleton.H" skelH
    mkCppFile "Skeleton.C" skelC
    let (prinH, prinC) = cf2CPPPrinter True (inPackage opts) cf
    mkCppFile "Printer.H" prinH
    mkCppFile "Printer.C" prinC
    mkCppFile "Test.C" (cpptest (inPackage opts) cf)
    Makefile.mkMakefile (optMake opts) $ makefile prefix name
  where
    name :: String
    name = lang opts
    -- The prefix is a string used by flex and bison
    -- that is prepended to generated function names.
    -- It should be a valid C identifier.
    prefix :: String
    prefix = snakeCase_ name ++ "_"
    parserMode :: ParserMode
    parserMode = CppParser (inPackage opts) prefix
    mkCppFile         x = mkfile x comment
    mkCppFileWithHint x = mkfile x commentWithEmacsModeHint

printParseErrHeader :: Maybe String -> String
printParseErrHeader inPackage =
  unlines
  [
     " #pragma once "
     , " #include <string>"
     , " #include <stdexcept>"
     , ""
     , nsStart inPackage
     , " class parse_error : public std::runtime_error"
     , " {"
     , " public:"
     , "     parse_error(int line, std::string str)"
     , "         : std::runtime_error(str)"
     , "         , m_line(line) {}"
     , "     int getLine() {"
     , "         return m_line;"
     , "     } "
     , " private:"
     , "     int m_line;"
     , " }; "
     , nsEnd inPackage
     ]

cpptest :: Maybe String -> CF -> String
cpptest inPackage cf = unlines $ concat
  [ testfileHeader
  , [ "",
    "#include <cstdio>",
    "#include <string>",
    "#include <iostream>",
    "#include \"Parser.H\"",
    "#include \"Printer.H\"",
    "#include \"Absyn.H\"",
    "#include \"ParserError.H\"",
    "",
    "void usage() {",
    "  printf(\"usage: Call with one of the following argument " ++
      "combinations:\\n\");",
    "  printf(\"\\t--help\\t\\tDisplay this help message.\\n\");",
    "  printf(\"\\t(no arguments)\\tParse stdin verbosely.\\n\");",
    "  printf(\"\\t(files)\\t\\tParse content of files verbosely.\\n\");",
    "  printf(\"\\t-s (files)\\tSilent mode. Parse content of files " ++
      "silently.\\n\");",
    "}",
    "",
    "int main(int argc, char ** argv)",
    "{",
    "  FILE *input;",
    "  int quiet = 0;",
    "  char *filename = NULL;",
    "",
    "  if (argc > 1) {",
    "    if (strcmp(argv[1], \"-s\") == 0) {",
    "      quiet = 1;",
    "      if (argc > 2) {",
    "        filename = argv[2];",
    "      } else {",
    "        input = stdin;",
    "      }",
    "    } else {",
    "      filename = argv[1];",
    "    }",
    "  }",
    "",
    "  if (filename) {",
    "    input = fopen(filename, \"r\");",
    "    if (!input) {",
    "      usage();",
    "      exit(1);",
    "    }",
    "  } else input = stdin;",
    "  /* The default entry point is used. For other options see Parser.H */",
    "  " ++ scope ++ dat ++ " *parse_tree = NULL;",
    "  try { ",
    "  parse_tree = " ++ scope ++ "p" ++ def ++ "(input);",
    "  } catch( " ++ scope ++ "parse_error &e) {",
    "     std::cerr << \"Parse error on line \" << e.getLine() << \"\\n\"; ",
    "  }",
    "  if (parse_tree)",
    "  {",
    "    printf(\"\\nParse Successful!\\n\");",
    "    if (!quiet) {",
    "      printf(\"\\n[Abstract Syntax]\\n\");",
    "      " ++ scope ++ "ShowAbsyn *s = new " ++ scope ++ "ShowAbsyn();",
    "      printf(\"%s\\n\\n\", s->show(parse_tree));",
    "      printf(\"[Linearized Tree]\\n\");",
    "      " ++ scope ++ "PrintAbsyn *p = new " ++ scope ++ "PrintAbsyn();",
    "      printf(\"%s\\n\\n\", p->print(parse_tree));",
    "    }",
    "    delete(parse_tree);",
    "    return 0;",
    "  }",
    "  return 1;",
    "}",
    ""
    ]
  ]
  where
   cat = firstEntry cf
   dat = identCat $ normCat cat
   def = identCat cat
   scope = nsScope inPackage

mkHeaderFile :: Maybe String -> [Cat] -> String
mkHeaderFile inPackage eps = unlines $ concat
  [ [ "#ifndef " ++ hdef
    , "#define " ++ hdef
    , ""
    , "#include<vector>"
    , "#include<string>"
    , "#include<cstdio>"
    , "#include \"Absyn.H\""
    , ""
    , nsStart inPackage
    ]
  , concatMap mkEntryFuncs eps
  , [ nsEnd inPackage
    , ""
    , "#endif"
    ]
  ]
  where
  hdef = nsDefine inPackage "PARSER_HEADER_FILE"

mkEntryFuncs :: Cat -> [String]
mkEntryFuncs s =
    [ identCat (normCat s) ++ "*" +++ "p" ++ identCat s ++ "(FILE *inp);"
    , identCat (normCat s) ++ "*" +++ "p" ++ identCat s ++ "(const char *str);"
    ]
