packages feed

futhark-0.18.6: src/Futhark/Test/Values/Parser.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Megaparsec-based parser for primitive 'Value's.  The difference
-- between this and the parser defined in "Futhark.Test.Values" is
-- that we don't try to handle both the textual and binary format -
-- only the former.  On the other hand, this parser has (much) better
-- error messages and can be easily used by other parsers (like the
-- ones for FutharkScript or test blocks.
module Futhark.Test.Values.Parser
  ( parsePrimType,
    parseType,
    parsePrimValue,
    parseValue,
  )
where

import Control.Monad.Except
import Data.Functor
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Vector.Storable as SVec
import Data.Void
import Futhark.Test.Values
import qualified Language.Futhark.Syntax as F
import Text.Megaparsec
import Text.Megaparsec.Char.Lexer
  ( binary,
    decimal,
    float,
    hexadecimal,
    signed,
  )

type Parser = Parsec Void T.Text

-- | Parse the name of a primitive type.  Does *not* consume any
-- trailing whitespace, nor does it permit any internal whitespace.
parsePrimType :: Parser F.PrimType
parsePrimType =
  choice
    [ "i8" $> F.Signed F.Int8,
      "i16" $> F.Signed F.Int16,
      "i32" $> F.Signed F.Int32,
      "i64" $> F.Signed F.Int64,
      "u8" $> F.Unsigned F.Int8,
      "u16" $> F.Unsigned F.Int16,
      "u32" $> F.Unsigned F.Int32,
      "u64" $> F.Unsigned F.Int64,
      "f32" $> F.FloatType F.Float32,
      "f64" $> F.FloatType F.Float64,
      "bool" $> F.Bool
    ]

parseInteger :: Parser Integer
parseInteger =
  signed (pure ()) $
    choice
      [ "0b" *> binary,
        "0x" *> hexadecimal,
        decimal
      ]

parseIntConst :: Parser F.PrimValue
parseIntConst = do
  x <- parseInteger
  notFollowedBy $ "f32" <|> "f64" <|> "."
  choice
    [ signedV F.Int8Value x "i8",
      signedV F.Int16Value x "i16",
      signedV F.Int32Value x "i32",
      signedV F.Int64Value x "i64",
      unsignedV F.Int8Value x "u8",
      unsignedV F.Int16Value x "u16",
      unsignedV F.Int32Value x "u32",
      unsignedV F.Int64Value x "u64",
      signedV F.Int32Value x ""
    ]
  where
    signedV mk x suffix =
      suffix $> F.SignedValue (mk (fromInteger x))
    unsignedV mk x suffix =
      suffix $> F.UnsignedValue (mk (fromInteger x))

parseFloatConst :: Parser F.PrimValue
parseFloatConst =
  choice
    [ "f32.nan" $> F.FloatValue (F.Float32Value (0 / 0)),
      "f64.nan" $> F.FloatValue (F.Float64Value (0 / 0)),
      "f32.inf" $> F.FloatValue (F.Float32Value (1 / 0)),
      "f64.inf" $> F.FloatValue (F.Float64Value (1 / 0)),
      "-f32.inf" $> F.FloatValue (F.Float32Value (-1 / 0)),
      "-f64.inf" $> F.FloatValue (F.Float64Value (-1 / 0)),
      numeric
    ]
  where
    numeric = do
      x <-
        signed (pure ()) $ choice [try float, fromInteger <$> decimal]
      choice
        [ floatV F.Float32Value x "f32",
          floatV F.Float64Value x "f64",
          floatV F.Float64Value x ""
        ]

    floatV mk x suffix =
      suffix $> F.FloatValue (mk (realToFrac (x :: Double)))

-- | Parse a primitive value.  Does *not* consume any trailing
-- whitespace, nor does it permit any internal whitespace.
parsePrimValue :: Parser F.PrimValue
parsePrimValue =
  choice
    [ try parseIntConst,
      parseFloatConst,
      "true" $> F.BoolValue True,
      "false" $> F.BoolValue False
    ]

lexeme :: Parser () -> Parser a -> Parser a
lexeme sep p = p <* sep

inBrackets :: Parser () -> Parser a -> Parser a
inBrackets sep = between (lexeme sep "[") (lexeme sep "]")

-- | Parse a type.  Does *not* consume any trailing whitespace, nor
-- does it permit any internal whitespace.
parseType :: Parser ValueType
parseType = ValueType <$> many parseDim <*> parsePrimType
  where
    parseDim = fromInteger <$> ("[" *> parseInteger <* "]")

parseEmpty :: Parser Value
parseEmpty = do
  ValueType dims t <- parseType
  unless (product dims == 0) $ fail "Expected at least one empty dimension"
  pure $ case t of
    F.Signed F.Int8 -> Int8Value (SVec.fromList dims) mempty
    F.Signed F.Int16 -> Int16Value (SVec.fromList dims) mempty
    F.Signed F.Int32 -> Int32Value (SVec.fromList dims) mempty
    F.Signed F.Int64 -> Int64Value (SVec.fromList dims) mempty
    F.Unsigned F.Int8 -> Word8Value (SVec.fromList dims) mempty
    F.Unsigned F.Int16 -> Word16Value (SVec.fromList dims) mempty
    F.Unsigned F.Int32 -> Word32Value (SVec.fromList dims) mempty
    F.Unsigned F.Int64 -> Word64Value (SVec.fromList dims) mempty
    F.FloatType F.Float32 -> Float32Value (SVec.fromList dims) mempty
    F.FloatType F.Float64 -> Float64Value (SVec.fromList dims) mempty
    F.Bool -> BoolValue (SVec.fromList dims) mempty

-- | Parse a value, given a post-lexeme parser for whitespace.
parseValue :: Parser () -> Parser Value
parseValue sep =
  choice
    [ putValue' $ lexeme sep parsePrimValue,
      putValue' $ inBrackets sep (parseValue sep `sepBy` lexeme sep ","),
      lexeme sep $ "empty(" *> parseEmpty <* ")"
    ]
  where
    putValue' :: PutValue v => Parser v -> Parser Value
    putValue' p = do
      o <- getOffset
      x <- p
      case putValue x of
        Nothing ->
          parseError . FancyError o . S.singleton $
            ErrorFail "array is irregular or has elements of multiple types."
        Just v ->
          pure v