{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} module Typst.Util ( TypeSpec (..), makeElement, makeElementWithScope, makeFunction, makeFunctionWithScope, makeSymbolMap, argsToFields, nthArg, namedArg, allArgs ) where import Control.Monad (foldM) import Control.Monad.Reader (ReaderT (runReaderT), asks) import Data.List (foldl') import qualified Data.Map as M import qualified Data.Map.Ordered as OM import qualified Data.Sequence as Seq import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import qualified Data.Vector as V import Text.Parsec (getPosition) import Typst.Types data TypeSpec = One ValType | Many ValType deriving (Show, Eq) insertOM :: Ord k => k -> v -> OM.OMap k v -> OM.OMap k v insertOM k v m = m OM.|> (k, v) -- | Create element function with names for positional parameters. makeElement :: Maybe Identifier -> Identifier -> [(Identifier, TypeSpec)] -> (Identifier, Val) makeElement mbNamespace name specs = makeElementWithScope mbNamespace name specs mempty -- | Create element function with names for positional parameters. makeElementWithScope :: Maybe Identifier -> Identifier -> [(Identifier, TypeSpec)] -> M.Map Identifier Val -> (Identifier, Val) makeElementWithScope mbNamespace name specs scope = ( name, VFunction (Just qname) scope $ Function $ \args -> do pos <- getPosition fields <- argsToFields specs args pure $ VContent . Seq.singleton $ Elt qname (Just pos) fields ) where qname = case mbNamespace of Nothing -> name Just ns -> ns <> "." <> name argsToFields :: MonadFail m => [(Identifier, TypeSpec)] -> Arguments -> m (M.Map Identifier Val) argsToFields specs args' = OM.toMap . named <$> foldM go args' specs where hasType' TContent VContent {} = True hasType' TContent VString {} = True hasType' TContent VSymbol {} = True hasType' TString (VContent _) = True hasType' TTermItem VArray {} = True hasType' x y = hasType x y toType TContent x = VContent $ valToContent x toType TTermItem (VArray [VContent t, VContent d]) = VTermItem t d toType TTermItem (VArray [VContent t]) = VTermItem t mempty toType TTermItem _ = VTermItem mempty mempty toType TLabel (VContent [Lab t]) = VLabel t toType _ x = x go args (posname, Many ty) = do let (as, bs) = span (hasType' ty) (positional args) pure $ args { named = insertOM posname (VArray $ V.fromList $ map (toType ty) as) (named args), positional = bs } go args (posname, One ty) = case break (hasType' ty) (positional args) of ([], []) -> pure args (as, b : bs) -> pure $ args { named = insertOM posname (toType ty b) (named args), positional = as ++ bs } (_, []) -> pure args makeFunction :: (forall m'. Monad m' => ReaderT Arguments (MP m') Val) -> Val makeFunction f = VFunction Nothing mempty $ Function $ runReaderT f makeFunctionWithScope :: (forall m'. Monad m' => ReaderT Arguments (MP m') Val) -> M.Map Identifier Val -> Val makeFunctionWithScope f m = VFunction Nothing m $ Function $ runReaderT f nthArg :: (Monad m, FromVal a) => Int -> ReaderT Arguments (MP m) a nthArg num = getPositional (num - 1) >>= fromVal getPositional :: Monad m => Int -> ReaderT Arguments (MP m) Val getPositional idx = do xs <- asks positional if idx >= length xs then pure VNone else pure $ xs !! idx getNamed :: Monad m => Identifier -> ReaderT Arguments (MP m) (Maybe Val) getNamed ident = do m <- asks named pure $ OM.lookup ident m namedArg :: (Monad m, FromVal a) => Identifier -> a -> ReaderT Arguments (MP m) a namedArg ident@(Identifier _) defaultVal = do mbval <- getNamed ident case mbval of Just val -> fromVal val Nothing -> pure defaultVal allArgs :: Monad m => ReaderT Arguments (MP m) [Val] allArgs = asks positional makeSymbolMap :: [(Text, Bool, Text)] -> M.Map Identifier Symbol makeSymbolMap = foldl' go mempty where go :: M.Map Identifier Symbol -> (Text, Bool, Text) -> M.Map Identifier Symbol go m (name, accent, v) = case T.split (== '.') name of [] -> m (k : ks) -> M.alter ( \case Nothing -> Just $ Symbol v accent (addVariant ks v mempty) Just (Symbol dv da vs) -> Just $ Symbol dv' da variants where variants = addVariant ks v vs minModifiers = minimum $ map (Set.size . fst) variants shortestVariants = filter ((== minModifiers) . Set.size . fst) variants -- "When displaying a symbol, Typst selects the first -- from the variants that have all attached modifiers -- and the minimum number of other modifiers." dv' = case map snd shortestVariants of [] -> dv (x:_) -> x ) (Identifier k) m addVariant :: [Text] -> Text -> [(Set.Set Text, Text)] -> [(Set.Set Text, Text)] addVariant ks v = (++ [(Set.fromList ks, v)])