packages feed

futhark-0.27.1: src-testing/Language/Futhark/SyntaxTests.hs

{-# OPTIONS_GHC -fno-warn-orphans #-}

module Language.Futhark.SyntaxTests (tests) where

import Control.Applicative hiding (many, some)
import Data.Bifunctor
import Data.Char (isAlphaNum)
import Data.Functor
import Data.Map qualified as M
import Data.String
import Data.Text qualified as T
import Data.Void
import Language.Futhark
import Language.Futhark.Parser (SyntaxError (syntaxErrorMsg), parseExp, parseType)
import Language.Futhark.Primitive.Parse (keyword, lexeme)
import Language.Futhark.PrimitiveTests ()
import Test.QuickCheck
import Test.Tasty
import Text.Megaparsec
import Text.Megaparsec.Char.Lexer qualified as L
import Prelude

tests :: TestTree
tests = testGroup "Source SyntaxTests" []

instance Arbitrary BinOp where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary Uniqueness where
  arbitrary = elements [Unique, Nonunique]

instance Arbitrary PrimType where
  arbitrary =
    oneof
      [ Signed <$> arbitrary,
        Unsigned <$> arbitrary,
        FloatType <$> arbitrary,
        pure Bool
      ]

instance Arbitrary PrimValue where
  arbitrary =
    oneof
      [ SignedValue <$> arbitrary,
        UnsignedValue <$> arbitrary,
        FloatValue <$> arbitrary,
        BoolValue <$> arbitrary
      ]

-- The following dirty instances make it slightly nicer to write unit tests.

instance IsString VName where
  fromString s =
    let (tag, s') = bimap reverse (reverse . tail) $ span (/= '_') $ reverse s
     in VName (fromString s') (read tag)

pQualVName :: Parser (QualName VName)
pQualVName = do
  xs <- pVName `sepBy1` "."
  pure $ QualName (init xs) (last xs)

pQualName :: Parser (QualName Name)
pQualName = do
  xs <- pName `sepBy1` "."
  pure $ QualName (init xs) (last xs)

instance IsString (QualName VName) where
  fromString = fromStringParse pQualVName "QualName VName"

instance IsString (QualName Name) where
  fromString = fromStringParse pQualName "QualName Name"

instance IsString UncheckedTypeExp where
  fromString =
    either (error . T.unpack . syntaxErrorMsg) id
      . parseType "IsString UncheckedTypeExp"
      . fromString

type Parser = Parsec Void T.Text

braces, brackets, parens :: Parser a -> Parser a
braces = between (lexeme "{") (lexeme "}")
brackets = between (lexeme "[") (lexeme "]")
parens = between (lexeme "(") (lexeme ")")

constituent :: Char -> Bool
constituent c = isAlphaNum c || (c `elem` ("_/'+-=!&^<>*|%" :: String))

pName :: Parser Name
pName =
  lexeme . fmap nameFromString $ some $ satisfy constituent

pVName :: Parser VName
pVName = lexeme $ do
  (s, tag) <-
    satisfy constituent `manyTill_` try pTag <?> "variable name"
  pure $ VName (nameFromString s) tag
  where
    pTag =
      "_" *> L.decimal <* notFollowedBy (satisfy constituent)

pPrimType :: Parser PrimType
pPrimType =
  choice $
    map
      f
      [ Bool,
        Signed Int8,
        Signed Int16,
        Signed Int32,
        Signed Int64,
        Unsigned Int8,
        Unsigned Int16,
        Unsigned Int32,
        Unsigned Int64,
        FloatType Float32,
        FloatType Float64
      ]
  where
    f t = keyword (prettyText t) $> t

pUniqueness :: Parser Uniqueness
pUniqueness = choice [lexeme "*" $> Unique, pure Nonunique]

pSize :: Parser Size
pSize =
  brackets $
    choice
      [ flip sizeFromInteger mempty <$> lexeme L.decimal,
        flip sizeFromName mempty <$> pQualVName
      ]

pScalarNonFun :: Parser d -> Parser (ScalarTypeBase d Uniqueness)
pScalarNonFun pd =
  choice
    [ Prim <$> pPrimType,
      pTypeVar,
      tupleRecord <$> parens (pType pd `sepBy` lexeme ","),
      Record . M.fromList <$> braces (pField `sepBy1` lexeme ",")
    ]
  where
    pField = (,) <$> pName <* lexeme ":" <*> pType pd
    pTypeVar = TypeVar <$> pUniqueness <*> pQualVName <*> many pTypeArg
    pTypeArg =
      choice
        [ TypeArgDim <$> pd,
          TypeArgType . second (const NoUniqueness) <$> pTypeArgType
        ]
    pTypeArgType =
      choice
        [ Scalar . Prim <$> pPrimType,
          parens $ pType pd
        ]

pArrayType :: Parser d -> Parser (TypeBase d Uniqueness)
pArrayType pd =
  Array
    <$> pUniqueness
    <*> (Shape <$> some pd)
    <*> (second (const NoUniqueness) <$> pScalarNonFun pd)

pNonFunType :: Parser d -> Parser (TypeBase d Uniqueness)
pNonFunType pd =
  choice
    [ try $ pArrayType pd,
      try $ parens $ pType pd,
      Scalar <$> pScalarNonFun pd
    ]

uniquenessToDiet :: Uniqueness -> Diet
uniquenessToDiet Unique = Consume
uniquenessToDiet Nonunique = Observe

pScalarType :: Parser d -> Parser (ScalarTypeBase d Uniqueness)
pScalarType pd = choice [try pFun, pScalarNonFun pd]
  where
    pFun =
      pParam <* lexeme "->" <*> pRetType pd
    pParam =
      choice
        [ try pNamedParam,
          do
            t <- pNonFunType pd
            pure $ Arrow Nonunique Unnamed (diet $ second uniquenessToDiet t) (toStruct t)
        ]
    pNamedParam = parens $ do
      v <- pVName <* lexeme ":"
      t <- pType pd
      pure $ Arrow Nonunique (Named v) (diet $ second uniquenessToDiet t) (toStruct t)

pRetType :: Parser d -> Parser (RetTypeBase d Uniqueness)
pRetType pd =
  choice
    [ lexeme "?" *> (RetType <$> some (brackets pVName) <* lexeme "." <*> pType pd),
      RetType [] <$> pType pd
    ]

pType :: Parser d -> Parser (TypeBase d Uniqueness)
pType pd =
  choice [try $ Scalar <$> pScalarType pd, pArrayType pd, parens (pType pd)]

fromStringParse :: Parser a -> String -> String -> a
fromStringParse p what s =
  either onError id $ parse (p <* eof) "" (T.pack s)
  where
    onError e =
      error $ "not a " <> what <> ": " <> s <> "\n" <> errorBundlePretty e

instance IsString (ScalarTypeBase Size NoUniqueness) where
  fromString =
    fromStringParse
      (second (const NoUniqueness) <$> pScalarType pSize)
      "ScalarType"

instance IsString (ScalarTypeBase () NoUniqueness) where
  fromString =
    fromStringParse
      (second (const NoUniqueness) <$> pScalarType (pure ()))
      "ScalarType"

instance IsString (TypeBase () NoUniqueness) where
  fromString =
    fromStringParse
      (second (const NoUniqueness) <$> pType (brackets $ pure ()))
      "Type"

instance IsString StructType where
  fromString =
    fromStringParse (second (const NoUniqueness) <$> pType pSize) "StructType"

instance IsString ParamType where
  fromString =
    fromStringParse (resToParam <$> pType pSize) "ParamType"

instance IsString ResType where
  fromString =
    fromStringParse (pType pSize) "ResType"

instance IsString StructRetType where
  fromString =
    fromStringParse (second (pure NoUniqueness) <$> pRetType pSize) "StructRetType"

instance IsString ResRetType where
  fromString = fromStringParse (pRetType pSize) "ResRetType"

instance IsString UncheckedExp where
  fromString =
    either (error . T.unpack . syntaxErrorMsg) id
      . parseExp "string literal"
      . T.pack