{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Functions common to different backends.

module BNFC.Backend.Common
  ( unicodeAndSymbols
  , asciiKeywords
  , flexEps
  , switchByPrecedence
  )
  where

import Prelude hiding ((<>))

-- import Data.Bifunctor   ( second )
import Data.Char

import BNFC.CF
import BNFC.Utils       ( (>.>) )
import BNFC.PrettyPrint

-- Andreas, 2020-10-08, issue #292:
-- Since the produced lexer for Haskell and Ocaml only recognizes ASCII identifiers,
-- but cfgKeywords also contains those using unicode characters,
-- we have to reclassify any keyword using non-ASCII characters
-- as symbol.
unicodeAndSymbols :: CF -> [String]
unicodeAndSymbols cf = filter (not . all isAscii) (cfgKeywords cf) ++ cfgSymbols cf

asciiKeywords :: CF -> [String]
asciiKeywords = filter (all isAscii) . cfgKeywords

-- | Representation of the empty word as Flex regular expression
flexEps :: String
flexEps = "[^.\\n]?"

-- UNUSED
-- -- | Helper function for c-like languages that generates the code printing
-- -- the list separator according to the given precedence level:
-- --
-- -- >>> let my_render c = "my_render(\"" <> text c <> "\")"
-- -- >>> renderListSepByPrecedence "x" my_render []
-- -- <BLANKLINE>
-- --
-- -- >>> renderListSepByPrecedence "x" my_render [(0,",")]
-- -- my_render(",");
-- --
-- -- >>> renderListSepByPrecedence "x" my_render [(3,";"), (1, "--")]
-- -- switch(x)
-- -- {
-- --   case 3: my_render(";"); break;
-- --   case 1: my_render("--"); break;
-- -- }
-- renderListSepByPrecedence
--   :: Doc                 -- ^ Name of the coercion level variable
--   -> (String -> Doc)     -- ^ render function
--   -> [(Integer, String)] -- ^ separators by precedence
--   -> Doc
-- renderListSepByPrecedence var render =
--   vcat . switchByPrecedence var . map (second $ render >.> (<> ";"))

-- Note (Andreas, 2021-05-02):
-- @renderListSepByPrecedence@ did not account for mixfix lists (issue #358)
-- and has been replaced by the more general @switchByPrecedence@.

switchByPrecedence
  :: Doc              -- ^ Name of the coercion level variable/
  -> [(Integer, Doc)] -- ^ Content by precedence.
  -> [Doc]
switchByPrecedence var = filter (not . isEmpty . snd) >.> \case
  []        -> []
  [(_,doc)] -> [ doc  ]
  ds        ->
    [ "switch(" <> var <> ")"
    , codeblock 2
      [ "case" <+> integer i <:> doc <+> "break;" | (i, doc) <- ds ]
    -- , codeblock 2 $ concat
    --   [ [ "case" <+> integer i <:> doc <+> "break;" |     (i, doc) <- init ds ]
    --   , [ "default" <:> doc                         | let (i, doc) =  last ds ]
    --   ]
    ]
    where
    a <:> b = a <> ":" <+> b
