Types.hs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Potrero.Types where
  3. import qualified Control.Exception as Exn
  4. import qualified Data.Map.Strict as Map
  5. import qualified Data.Set as Set
  6. import qualified Data.Text as Text
  7. import qualified System.Random as Rand
  8. type TableName = Text.Text
  9. type Roll = Int
  10. data Range = Range { rFrom :: Roll, rTo :: Roll }
  11. deriving (Eq, Show)
  12. class Exn.Exception t => PotreroError t where
  13. potreroMessage :: t -> [Text.Text]
  14. data BadTable = BadTable
  15. { badTableTable :: Table
  16. , badTableIndex :: [Int]
  17. } deriving (Eq, Show)
  18. instance Exn.Exception BadTable where
  19. instance PotreroError BadTable where
  20. potreroMessage bt =
  21. let rolls = map (Text.pack . show) (badTableIndex bt)
  22. results = [ "`" <> Text.strip (showResult r) <> "`"
  23. | (Range x y, r) <- tableChoices (badTableTable bt)
  24. , or [ i >= x && i <= y | i <- badTableIndex bt]
  25. ]
  26. orText = Text.intercalate " or "
  27. in [ "Malformed table: `" <> tableName (badTableTable bt) <> "`"
  28. , " a roll of " <> orText rolls <> " is ambiguous"
  29. , " and may result in either " <> orText results
  30. ]
  31. -- needed to handle d66 tables
  32. rangeMap :: Table -> [(Range, Result)] -> [Int]
  33. rangeMap t ranges = Set.toList (go Set.empty ranges)
  34. where
  35. go set [] = set
  36. go set ((Range x y, _result):rs) =
  37. let rangeSet = Set.fromList [x..y]
  38. overlap = Set.intersection rangeSet set
  39. in if Set.null overlap
  40. then go (Set.union rangeSet set) rs
  41. else Exn.throw (BadTable t (Set.toList overlap))
  42. type TableMap = Map.Map TableName Table
  43. data Table = Table
  44. { tableName :: Text.Text
  45. , tableChoices :: [(Range, Result)]
  46. } deriving (Eq, Show)
  47. data Fragment
  48. = FragText Text.Text
  49. | FragRoll Text.Text
  50. deriving (Eq, Show)
  51. data Result = Result { fromResult :: [Fragment] }
  52. deriving (Eq, Show)
  53. showResult :: Result -> Text.Text
  54. showResult = foldMap go . fromResult
  55. where go (FragText t) = t
  56. go (FragRoll n) = "@{" <> n <> "}"
  57. -- * Values
  58. data Value = Value
  59. { valueMsg :: Text.Text
  60. , valueFrom :: TableName
  61. , valueResult :: Roll
  62. , valueSources :: [Value]
  63. } deriving (Eq, Show)
  64. concatValues :: Context -> [Value] -> Value
  65. concatValues _ [v] = v
  66. concatValues Context { ctxSelf = table, ctxRoll = roll } vs = Value
  67. { valueMsg = foldMap valueMsg vs
  68. , valueFrom = table
  69. , valueResult = roll
  70. , valueSources = vs
  71. }
  72. bareValue :: Context -> Text.Text -> Value
  73. bareValue Context { ctxSelf = table, ctxRoll = roll } text = Value
  74. { valueMsg = text
  75. , valueFrom = table
  76. , valueResult = roll
  77. , valueSources = []
  78. }
  79. stripValue :: Value -> Value
  80. stripValue value = value { valueMsg = Text.strip (valueMsg value) }
  81. -- * Exceptions
  82. data NoSuchTable = NoSuchTable Text.Text
  83. deriving (Eq, Show)
  84. instance Exn.Exception NoSuchTable where
  85. -- * Context
  86. data Context = Context
  87. { ctxMap :: TableMap
  88. , ctxRoll :: Int
  89. , ctxSelf :: Text.Text
  90. }
  91. -- * Evaluating Tables
  92. findTable :: Text.Text -> Context -> Maybe Table
  93. findTable name ctx = Map.lookup name (ctxMap ctx)
  94. computeFragments :: Context -> Fragment -> IO Value
  95. computeFragments ctx (FragText msg) = pure (bareValue ctx msg)
  96. computeFragments ctx (FragRoll name) =
  97. let absolute = case Text.stripPrefix "self" name of
  98. Just rest -> ctxSelf ctx <> rest
  99. Nothing -> name
  100. in case findTable absolute ctx of
  101. Just t -> rollTable (ctxMap ctx) t
  102. Nothing -> Exn.throwIO (NoSuchTable absolute)
  103. computeResult :: Context -> Result -> IO Value
  104. computeResult ctx (Result msgs) = do
  105. vs <- mapM (computeFragments ctx) msgs
  106. pure (concatValues ctx vs)
  107. rollTable :: TableMap -> Table -> IO Value
  108. rollTable tables t = do
  109. let rmap = rangeMap t (tableChoices t)
  110. rollIdx <- Rand.randomRIO (0, length rmap - 1)
  111. let roll = rmap !! rollIdx
  112. ctx = Context
  113. { ctxMap = tables
  114. , ctxRoll = roll
  115. , ctxSelf = tableName t
  116. }
  117. case [ result
  118. | (range, result) <- tableChoices t
  119. , roll >= rFrom range && roll <= rTo range
  120. ] of
  121. [choice] -> stripValue <$> computeResult ctx choice
  122. _ -> error "unreachable"