packages feed

boilerplate-0.0.2: library/Boilerplate/Interpreter.hs

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

module Boilerplate.Interpreter (interpretRule) where

import Boilerplate.Types
import Data.List (intersect)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Text (Text)
import qualified Data.Text as T
import HsInspect.Types (Type(..))

data Ctx = G -- ^^ global context
         | P Text [(Maybe Text, Text, TyCtx)] -- ^^ product-like thing: cons [(maybe fieldname, param type, typarams)]
         | F Text Int (Maybe Text) Text TyCtx -- ^^ field in a product-like: cons idx fieldname param type typarams
         | T Text -- ^^ type parameter field
  deriving (Eq, Show)

data TyCtx = Poly | Higher | Concrete
  deriving (Eq, Show)

interpretRule :: Rule -> Type -> Map Text Custom -> Either Text Text
interpretRule (Rule atoms) tpe options = T.strip <$> interpretTree G atoms
  where
    interpretTree :: Ctx -> Tree -> Either Text Text
    interpretTree ctx t = T.concat <$> traverse (interpret ctx) t

    -- Several codepaths are impossible because of the way the Rule is parsed. We
    -- could have done some fancy type magic to avoid having to consider these
    -- cases, but it is far easier and simpler to just take the hit in the
    -- interpreter.
    impossible :: String -> a
    impossible ctx = error $ "impossible " <> ctx

    showt :: Show a => a -> Text
    showt = T.pack . show

    param :: Int -> Int -> Text
    param n p = "p_" <> showt n <> "_" <> showt p

    tyCtx :: [Text] -> Text -> [Text] -> TyCtx
    tyCtx type_params tpe tpe_params =
      if elem tpe type_params
      then Poly
      else if not . null $ intersect type_params tpe_params
      then Higher
      else Concrete

    -- no context interpreter
    interpret ctx = \case
      Raw txt -> Right txt
      Type -> Right $ case tpe of
        ProductType tn _ _ _ _ -> tn
        RecordType tn _ _ _ _ -> tn
        SumType tn _ _ -> tn
      TParams empty prefix els sep suffix ->
        if null tparams
        then interpretTree G empty
        else (\p b -> p <> b <> suffix) <$> interpretTree G prefix <*> body
        where
          tparams = T <$> case tpe of
            ProductType _ tps _ _ _ -> tps
            RecordType _ tps _ _ _ -> tps
            SumType _ tps _ -> tps
          body = T.intercalate sep <$> traverse (flip interpretTree els) tparams
      TParam -> case ctx of
        T p -> Right p
        _ -> impossible "TParam"
      Product els ->
        let interpret' c ps = interpretTree (P c ps) els
         in case tpe of
        SumType _ _ _ -> Right T.empty
        ProductType _ tps _ cons params -> interpret' cons $ (\(tpe, tys) -> (Nothing, tpe, tyCtx tps tpe tys)) <$> params
        RecordType _ tps _ cons params -> interpret' cons $ (\(nme, tpe, tys) -> (Just nme, tpe, tyCtx tps tpe tys)) <$> params
      Sum prefix els sep suffix -> case tpe of
        ProductType _ _ _ _ _ -> Right T.empty
        RecordType _ _ _ _ _ -> Right T.empty
        SumType _ tps tags -> (\b -> prefix <> b <> suffix) <$> body
          where
            tags' = (\(cons, tpes) -> P cons ((\(tpe, tys) -> (Nothing, tpe, tyCtx tps tpe tys)) <$> tpes)) <$> tags
            body = T.intercalate sep <$> traverse (flip interpretTree els) tags'
      Uncons n -> case ctx of
        P cons [] -> Right cons
        P cons ps -> Right $ "(" <> body <> ")"
          where
            body = T.intercalate " " $ cons : ((param n . fst) <$> zip [1..] ps)
        _ -> impossible "Uncons"
      Cons -> case ctx of
        P cons _ -> Right cons
        F cons _ _ _ _ -> Right cons
        _ -> impossible "Cons"
      Field empty prefix els sep suffix -> case ctx of
        P _ [] -> interpretTree ctx empty
        P cons ps -> (\p b -> p <> b <> suffix) <$> interpretTree ctx prefix <*> body
          where
            fields = (\(n, (f, t, tc)) -> F cons n f t tc) <$> zip [1..] ps
            body = T.intercalate sep <$> traverse (flip interpretTree els) fields
        _ -> impossible "Field"
      Param n ->  case ctx of
        F _ p _ _ _ -> Right $ param n p
        _ -> impossible "Param"
      FieldName -> case ctx of
        F _ _ n _ _ -> maybe (Left "field names are required") Right n
        _ -> impossible "FieldName"
      FieldType -> case ctx of
        F _ _ _ t _ -> Right t
        _ -> impossible "FieldType"
      TyCase poly higher concrete -> case ctx of
        F _ _ _ _ ty -> interpretTree ctx $ case ty of
          Poly -> poly
          Higher -> higher
          Concrete -> concrete
        _ -> impossible "TyCase"
      Custom sym fallback ->
        let err = Left $ "missing required option '" <> sym <> "' which should be " <> ctx'
            ctx' = case ctx of
              G -> "a global value"
              T _ -> "a global value"
              P cons _ -> "a mapping for the data constructors of " <> cons
              F cons _ _ _ _ -> "an indexed sequence for fields of the data constructor " <> cons
         in case (M.lookup sym options, ctx) of
          (Just (Global txt), _) -> Right txt
          (Just (Indexed vs), F _ p _ _ _) -> maybe err Right $ atMay (p - 1) vs
          (Just (Named vs), F _ _ (Just f) _ _) -> maybe err Right $ M.lookup f vs
          (Just (NamedIndexed vs), F cons p _ _ _) -> maybe err Right $ atMay (p - 1) =<< M.lookup cons vs
          (Just (Named vs), P cons _) -> maybe err Right $ M.lookup cons vs
          _ -> case fallback of
            Nothing -> err
            Just t -> interpretTree ctx t
      Sugar sugar -> interpretTree ctx $ case sugar of
        Instance tc ->
          [Raw "instance ",
           TParams [] [Raw "("] [Raw tc, Raw " ", TParam] ", " ") => ", Raw tc, Raw " ",
           TParams [Type] [Raw "(", Type, Raw " "] [TParam] " " ")", Raw " where"]
        Data tree ->
          [Product tree, Sum "" tree "" ""]

atMay :: Int -> [a] -> Maybe a
atMay i as | i < 0 = Nothing
        | otherwise = f i as
    where f 0 (x : _) = Just x
          f i' (_ : as') = f (i' - 1) as'
          f _ [] = Nothing