packages feed

swarm-0.7.0.0: src/swarm-lang/Swarm/Language/Parser/Value.hs

{-# LANGUAGE OverloadedStrings #-}

-- |
-- SPDX-License-Identifier: BSD-3-Clause
--
-- Parse values of the Swarm language, indexed by type, by running the
-- full swarm-lang parser and then checking that the result is a value
-- of the proper type.
module Swarm.Language.Parser.Value (readValue) where

import Control.Lens ((^.))
import Data.Either.Extra (eitherToMaybe)
import Data.Text (Text)
import Data.Text qualified as T
import Swarm.Language.Context qualified as Ctx
import Swarm.Language.Key (parseKeyComboFull)
import Swarm.Language.Parser (readNonemptyTerm)
import Swarm.Language.Syntax
import Swarm.Language.Typecheck (checkTop)
import Swarm.Language.Types (Type, emptyTDCtx)
import Swarm.Language.Value
import Text.Megaparsec qualified as MP

readValue :: Type -> Text -> Maybe Value
readValue ty txt = do
  -- Try to strip off a prefix representing a printable entity.  Look
  -- for the first colon or double quote.  We will ignore a colon if a
  -- double quote comes before it, because a colon could legitimately
  -- occur in a formatted Text value, e.g. "\"hi: there\"".  Otherwise,
  -- strip off anything occurring before the first colon.
  --
  -- Note, this would break if we ever had a printable entity whose
  -- name contains a colon; printing on such an entity would yield
  -- entity names like "Magic: The Gathering: 6" for which `read`, as
  -- implemented here, would not work correctly. However, that seems
  -- unlikely.
  let firstUnquotedColon = T.dropWhile (\c -> c /= ':' && c /= '"') txt
  let txt' = case T.uncons firstUnquotedColon of
        Nothing -> txt
        Just ('"', _) -> txt
        Just (':', t) -> t
        _ -> txt
  s <- eitherToMaybe $ readNonemptyTerm txt'
  _ <- eitherToMaybe $ checkTop Ctx.empty Ctx.empty emptyTDCtx s ty
  toValue $ s ^. sTerm

toValue :: Term -> Maybe Value
toValue = \case
  TUnit -> Just VUnit
  TDir d -> Just $ VDir d
  TInt n -> Just $ VInt n
  TText t -> Just $ VText t
  TBool b -> Just $ VBool b
  TApp (TConst c) t2 -> case c of
    Neg -> toValue t2 >>= negateInt
    Inl -> VInj False <$> toValue t2
    Inr -> VInj True <$> toValue t2
    Key -> do
      VText k <- toValue t2
      VKey <$> eitherToMaybe (MP.runParser parseKeyComboFull "" k)
    _ -> Nothing
  TPair t1 t2 -> VPair <$> toValue t1 <*> toValue t2
  TRcd m -> VRcd <$> traverse (>>= toValue) m
  TParens t -> toValue t
  -- List the other cases explicitly, instead of a catch-all, so that
  -- we will get a warning if we ever add new constructors in the
  -- future
  TConst {} -> Nothing
  TAntiInt {} -> Nothing
  TAntiText {} -> Nothing
  TRequire {} -> Nothing
  TStock {} -> Nothing
  TRequirements {} -> Nothing
  TVar {} -> Nothing
  TLam {} -> Nothing
  TApp {} -> Nothing
  TLet {} -> Nothing
  TTydef {} -> Nothing
  TBind {} -> Nothing
  TDelay {} -> Nothing
  TProj {} -> Nothing
  TAnnotate {} -> Nothing
  TSuspend {} -> Nothing

-- TODO(#2232): in order to get `read` to work for delay, function,
-- and/or command types, we will need to handle a few more of the
-- above cases, e.g. TConst, TLam, TApp, TLet, TBind, TDelay.

negateInt :: Value -> Maybe Value
negateInt = \case
  VInt n -> Just (VInt (-n))
  _ -> Nothing