packages feed

grisette-0.9.0.0: src/Grisette/Internal/Core/Data/SExpr.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveLift #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}

{-# HLINT ignore "Unused LANGUAGE pragma" #-}

-- |
-- Module      :   Grisette.Internal.Core.Data.SExpr
-- Copyright   :   (c) Sirui Lu 2024
-- License     :   BSD-3-Clause (see the LICENSE file)
--
-- Maintainer  :   siruilu@cs.washington.edu
-- Stability   :   Experimental
-- Portability :   GHC only
module Grisette.Internal.Core.Data.SExpr
  ( SExpr (..),
    showsSExprWithParens,
    parseFileLocation,
    fileLocation,
  )
where

import Control.DeepSeq (NFData)
import qualified Data.Binary as Binary
import Data.Bytes.Serial (Serial (deserialize, serialize))
import Data.Hashable (Hashable)
import qualified Data.Serialize as Cereal
import Data.Serialize.Text ()
import qualified Data.Text as T
import Debug.Trace.LocationTH (__LOCATION__)
import GHC.Generics (Generic)
import Language.Haskell.TH.Syntax (Lift, unsafeTExpCoerce)
import Language.Haskell.TH.Syntax.Compat (SpliceQ, liftSplice)

-- | S-expression data type. Used for symbol metadata.
data SExpr = Atom T.Text | List [SExpr] | NumberAtom Integer | BoolAtom Bool
  deriving stock (Eq, Ord, Generic, Lift)
  deriving anyclass (Hashable, NFData, Serial)

instance Cereal.Serialize SExpr where
  put = serialize
  get = deserialize

instance Binary.Binary SExpr where
  put = serialize
  get = deserialize

instance Show SExpr where
  showsPrec _ = showsSExprWithParens '(' ')'

unwordsS :: [ShowS] -> ShowS
unwordsS [] = id
unwordsS [x] = x
unwordsS (x : xs) = x . showString " " . unwordsS xs

-- | Show an S-expression with specific parentheses.
showsSExprWithParens :: Char -> Char -> SExpr -> ShowS
showsSExprWithParens _ _ (Atom s) = showString $ T.unpack s
showsSExprWithParens lp rp (List l) =
  showString [lp] . unwordsS (map shows l) . (showString [rp])
showsSExprWithParens _ _ (NumberAtom n) = shows n
showsSExprWithParens _ _ (BoolAtom b) = showString $ if b then "#t" else "#f"

-- | Parse a file location string into an S-expression.
parseFileLocation :: String -> SExpr
parseFileLocation str =
  let r = reverse str
      (s2, r1) = break (== '-') r
      (s1, r2) = break (== ':') $ tail r1
      (l, p) = break (== ':') $ tail r2
   in List
        [ Atom "grisette-file-location",
          Atom $ T.pack $ reverse $ tail p,
          NumberAtom $ read $ reverse l,
          List
            [ NumberAtom $ read $ reverse s1,
              NumberAtom $ read $ reverse s2
            ]
        ]

-- | Get the file location of the splice.
fileLocation :: SpliceQ SExpr
fileLocation =
  [||parseFileLocation $$(liftSplice $ unsafeTExpCoerce __LOCATION__)||]