futhark-data-1.0.0.0: src/Futhark/Data/Parser.hs
{-# LANGUAGE OverloadedStrings #-}
-- | Megaparsec-based parser for 'Value's in the textual value format.
-- The difference between this and the reader defined in
-- "Futhark.Data.Reader" 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.Data.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.Data
import Text.Megaparsec
import Text.Megaparsec.Char.Lexer
( binary,
decimal,
float,
hexadecimal,
signed,
)
-- | Parse the name of a primitive type. Does *not* consume any
-- trailing whitespace, nor does it permit any internal whitespace.
parsePrimType :: Parsec Void T.Text PrimType
parsePrimType =
choice
[ "i8" $> I8,
"i16" $> I16,
"i32" $> I32,
"i64" $> I64,
"u8" $> U8,
"u16" $> U16,
"u32" $> U32,
"u64" $> U64,
"f32" $> F32,
"f64" $> F64,
"bool" $> Bool
]
parseInteger :: Parsec Void T.Text Integer
parseInteger =
signed (pure ()) $
choice
[ "0b" *> binary,
"0x" *> hexadecimal,
decimal
]
scalar :: SVec.Storable a => (Vector Int -> Vector a -> Value) -> a -> Value
scalar f x = f mempty (SVec.singleton x)
parseIntConst :: Parsec Void T.Text Value
parseIntConst = do
x <- parseInteger
notFollowedBy $ "f32" <|> "f64" <|> "." <|> "e"
choice
[ intV I8Value x "i8",
intV I16Value x "i16",
intV I32Value x "i32",
intV I64Value x "i64",
intV U8Value x "u8",
intV U16Value x "u16",
intV U32Value x "u32",
intV U64Value x "u64",
intV I32Value x ""
]
where
intV mk x suffix =
suffix $> scalar mk (fromInteger x)
parseFloatConst :: Parsec Void T.Text Value
parseFloatConst =
choice
[ "f32.nan" $> scalar F32Value (0 / 0),
"f64.nan" $> scalar F64Value (0 / 0),
"f32.inf" $> scalar F32Value (1 / 0),
"f64.inf" $> scalar F64Value (1 / 0),
"-f32.inf" $> scalar F32Value (-1 / 0),
"-f64.inf" $> scalar F64Value (-1 / 0),
numeric
]
where
numeric = do
x <-
signed (pure ()) $ choice [try float, fromInteger <$> decimal]
choice
[ floatV F32Value x "f32",
floatV F64Value x "f64",
floatV F64Value x ""
]
floatV mk x suffix =
suffix $> scalar mk (realToFrac (x :: Double))
-- | Parse a primitive value. Does *not* consume any trailing
-- whitespace, nor does it permit any internal whitespace.
parsePrimValue :: Parsec Void T.Text Value
parsePrimValue =
choice
[ try parseIntConst,
parseFloatConst,
"true" $> BoolValue mempty (SVec.singleton True),
"false" $> BoolValue mempty (SVec.singleton False)
]
lexeme :: Parsec Void T.Text () -> Parsec Void T.Text a -> Parsec Void T.Text a
lexeme sep p = p <* sep
inBrackets :: Parsec Void T.Text () -> Parsec Void T.Text a -> Parsec Void T.Text 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 :: Parsec Void T.Text ValueType
parseType = ValueType <$> many parseDim <*> parsePrimType
where
parseDim = fromInteger <$> ("[" *> parseInteger <* "]")
parseEmpty :: Parsec Void T.Text Value
parseEmpty = do
ValueType dims t <- parseType
unless (product dims == 0) $ fail "Expected at least one empty dimension"
pure $ case t of
I8 -> I8Value (SVec.fromList dims) mempty
I16 -> I16Value (SVec.fromList dims) mempty
I32 -> I32Value (SVec.fromList dims) mempty
I64 -> I64Value (SVec.fromList dims) mempty
U8 -> U8Value (SVec.fromList dims) mempty
U16 -> U16Value (SVec.fromList dims) mempty
U32 -> U32Value (SVec.fromList dims) mempty
U64 -> U64Value (SVec.fromList dims) mempty
F32 -> F32Value (SVec.fromList dims) mempty
F64 -> F64Value (SVec.fromList dims) mempty
Bool -> BoolValue (SVec.fromList dims) mempty
-- | Parse a value, given a post-lexeme parser for whitespace.
parseValue :: Parsec Void T.Text () -> Parsec Void T.Text Value
parseValue sep =
choice
[ lexeme sep parsePrimValue,
putValue' $ inBrackets sep (parseValue sep `sepBy` lexeme sep ","),
lexeme sep $ "empty(" *> parseEmpty <* ")"
]
where
putValue' :: PutValue v => Parsec Void T.Text v -> Parsec Void T.Text 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