module GraphQL.Internal.Syntax.Parser
( queryDocument
, schemaDocument
, value
) where
import Protolude hiding (option, takeWhile)
import Control.Applicative ((<|>), empty, many, optional)
import Control.Monad (fail)
import Data.Aeson.Parser (jstring)
import Data.Scientific (floatingOrInteger)
import Data.Text (find)
import qualified Data.Attoparsec.ByteString as A
import Data.Attoparsec.Text
( Parser
, (<?>)
, anyChar
, char
, match
, many1
, option
, scan
, scientific
, sepBy1
)
import qualified GraphQL.Internal.Syntax.AST as AST
import GraphQL.Internal.Syntax.Tokens (tok, whiteSpace)
queryDocument :: Parser AST.QueryDocument
queryDocument = whiteSpace *> (AST.QueryDocument <$> many1 definition) <?> "query document error!"
schemaDocument :: Parser AST.SchemaDocument
schemaDocument = whiteSpace *> (AST.SchemaDocument <$> many1 typeDefinition) <?> "type document error"
definition :: Parser AST.Definition
definition = AST.DefinitionOperation <$> operationDefinition
<|> AST.DefinitionFragment <$> fragmentDefinition
<?> "definition error!"
operationDefinition :: Parser AST.OperationDefinition
operationDefinition =
AST.Query <$ tok "query" <*> node
<|> AST.Mutation <$ tok "mutation" <*> node
<|> (AST.AnonymousQuery <$> selectionSet)
<?> "operationDefinition error!"
node :: Parser AST.Node
node = AST.Node <$> AST.nameParser
<*> optempty variableDefinitions
<*> optempty directives
<*> selectionSet
variableDefinitions :: Parser [AST.VariableDefinition]
variableDefinitions = parens (many1 variableDefinition)
variableDefinition :: Parser AST.VariableDefinition
variableDefinition =
AST.VariableDefinition <$> variable
<* tok ":"
<*> type_
<*> optional defaultValue
defaultValue :: Parser AST.DefaultValue
defaultValue = tok "=" *> value
variable :: Parser AST.Variable
variable = AST.Variable <$ tok "$" <*> AST.nameParser
selectionSet :: Parser AST.SelectionSet
selectionSet = braces $ many1 selection
selection :: Parser AST.Selection
selection = AST.SelectionField <$> field
<|> AST.SelectionInlineFragment <$> inlineFragment
<|> AST.SelectionFragmentSpread <$> fragmentSpread
<?> "selection error!"
field :: Parser AST.Field
field = AST.Field <$> option empty (pure <$> alias)
<*> AST.nameParser
<*> optempty arguments
<*> optempty directives
<*> optempty selectionSet
alias :: Parser AST.Alias
alias = AST.nameParser <* tok ":"
arguments :: Parser [AST.Argument]
arguments = parens $ many1 argument
argument :: Parser AST.Argument
argument = AST.Argument <$> AST.nameParser <* tok ":" <*> value
fragmentSpread :: Parser AST.FragmentSpread
fragmentSpread = AST.FragmentSpread
<$ tok "..."
<*> AST.nameParser
<*> optempty directives
inlineFragment :: Parser AST.InlineFragment
inlineFragment = AST.InlineFragment
<$ tok "..."
<*> optional (tok "on" *> typeCondition)
<*> optempty directives
<*> selectionSet
fragmentDefinition :: Parser AST.FragmentDefinition
fragmentDefinition = AST.FragmentDefinition
<$ tok "fragment"
<*> AST.nameParser
<* tok "on"
<*> typeCondition
<*> optempty directives
<*> selectionSet
typeCondition :: Parser AST.TypeCondition
typeCondition = namedType
value :: Parser AST.Value
value = tok (AST.ValueVariable <$> (variable <?> "variable")
<|> (number <?> "number")
<|> AST.ValueNull <$ tok "null"
<|> AST.ValueBoolean <$> (booleanValue <?> "booleanValue")
<|> AST.ValueString <$> (stringValue <?> "stringValue")
<|> AST.ValueEnum <$> (AST.nameParser <?> "name")
<|> AST.ValueList <$> (listValue <?> "listValue")
<|> AST.ValueObject <$> (objectValue <?> "objectValue")
<?> "value error!")
where
number = do
(numText, num) <- match (tok scientific)
case (Data.Text.find (== '.') numText, floatingOrInteger num) of
(Just _, Left r) -> pure (AST.ValueFloat r)
(Just _, Right i) -> pure (AST.ValueFloat (fromIntegral i))
(Nothing, Left r) -> pure (AST.ValueInt (floor r))
(Nothing, Right i) -> pure (AST.ValueInt i)
booleanValue :: Parser Bool
booleanValue = True <$ tok "true"
<|> False <$ tok "false"
stringValue :: Parser AST.StringValue
stringValue = do
parsed <- char '"' *> jstring_
case unescapeText parsed of
Left err -> fail err
Right escaped -> pure (AST.StringValue escaped)
where
jstring_ :: Parser Text
jstring_ = scan startState go <* anyChar
startState = False
go a c
| a = Just False
| c == '"' = Nothing
| otherwise = let a' = c == backslash
in Just a'
where backslash = '\\'
unescapeText str = A.parseOnly jstring ("\"" <> toS str <> "\"")
listValue :: Parser AST.ListValue
listValue = AST.ListValue <$> brackets (many value)
objectValue :: Parser AST.ObjectValue
objectValue = AST.ObjectValue <$> braces (many (objectField <?> "objectField"))
objectField :: Parser AST.ObjectField
objectField = AST.ObjectField <$> AST.nameParser <* tok ":" <*> value
directives :: Parser [AST.Directive]
directives = many1 directive
directive :: Parser AST.Directive
directive = AST.Directive
<$ tok "@"
<*> AST.nameParser
<*> optempty arguments
type_ :: Parser AST.Type
type_ = AST.TypeList <$> listType
<|> AST.TypeNonNull <$> nonNullType
<|> AST.TypeNamed <$> namedType
<?> "type_ error!"
namedType :: Parser AST.NamedType
namedType = AST.NamedType <$> AST.nameParser
listType :: Parser AST.ListType
listType = AST.ListType <$> brackets type_
nonNullType :: Parser AST.NonNullType
nonNullType = AST.NonNullTypeNamed <$> namedType <* tok "!"
<|> AST.NonNullTypeList <$> listType <* tok "!"
<?> "nonNullType error!"
typeDefinition :: Parser AST.TypeDefinition
typeDefinition =
AST.TypeDefinitionObject <$> objectTypeDefinition
<|> AST.TypeDefinitionInterface <$> interfaceTypeDefinition
<|> AST.TypeDefinitionUnion <$> unionTypeDefinition
<|> AST.TypeDefinitionScalar <$> scalarTypeDefinition
<|> AST.TypeDefinitionEnum <$> enumTypeDefinition
<|> AST.TypeDefinitionInputObject <$> inputObjectTypeDefinition
<|> AST.TypeDefinitionTypeExtension <$> typeExtensionDefinition
<?> "typeDefinition error!"
objectTypeDefinition :: Parser AST.ObjectTypeDefinition
objectTypeDefinition = AST.ObjectTypeDefinition
<$ tok "type"
<*> AST.nameParser
<*> optempty interfaces
<*> fieldDefinitions
interfaces :: Parser AST.Interfaces
interfaces = tok "implements" *> many1 namedType
fieldDefinitions :: Parser [AST.FieldDefinition]
fieldDefinitions = braces $ many1 fieldDefinition
fieldDefinition :: Parser AST.FieldDefinition
fieldDefinition = AST.FieldDefinition
<$> AST.nameParser
<*> optempty argumentsDefinition
<* tok ":"
<*> type_
argumentsDefinition :: Parser AST.ArgumentsDefinition
argumentsDefinition = parens $ many1 inputValueDefinition
interfaceTypeDefinition :: Parser AST.InterfaceTypeDefinition
interfaceTypeDefinition = AST.InterfaceTypeDefinition
<$ tok "interface"
<*> AST.nameParser
<*> fieldDefinitions
unionTypeDefinition :: Parser AST.UnionTypeDefinition
unionTypeDefinition = AST.UnionTypeDefinition
<$ tok "union"
<*> AST.nameParser
<* tok "="
<*> unionMembers
unionMembers :: Parser [AST.NamedType]
unionMembers = namedType `sepBy1` tok "|"
scalarTypeDefinition :: Parser AST.ScalarTypeDefinition
scalarTypeDefinition = AST.ScalarTypeDefinition
<$ tok "scalar"
<*> AST.nameParser
enumTypeDefinition :: Parser AST.EnumTypeDefinition
enumTypeDefinition = AST.EnumTypeDefinition
<$ tok "enum"
<*> AST.nameParser
<*> enumValueDefinitions
enumValueDefinitions :: Parser [AST.EnumValueDefinition]
enumValueDefinitions = braces $ many1 enumValueDefinition
enumValueDefinition :: Parser AST.EnumValueDefinition
enumValueDefinition = AST.EnumValueDefinition <$> AST.nameParser
inputObjectTypeDefinition :: Parser AST.InputObjectTypeDefinition
inputObjectTypeDefinition = AST.InputObjectTypeDefinition
<$ tok "input"
<*> AST.nameParser
<*> inputValueDefinitions
inputValueDefinitions :: Parser [AST.InputValueDefinition]
inputValueDefinitions = braces $ many1 inputValueDefinition
inputValueDefinition :: Parser AST.InputValueDefinition
inputValueDefinition = AST.InputValueDefinition
<$> AST.nameParser
<* tok ":"
<*> type_
<*> optional defaultValue
typeExtensionDefinition :: Parser AST.TypeExtensionDefinition
typeExtensionDefinition = AST.TypeExtensionDefinition
<$ tok "extend"
<*> objectTypeDefinition
parens :: Parser a -> Parser a
parens = between "(" ")"
braces :: Parser a -> Parser a
braces = between "{" "}"
brackets :: Parser a -> Parser a
brackets = between "[" "]"
between :: Parser Text -> Parser Text -> Parser a -> Parser a
between open close p = tok open *> p <* tok close
optempty :: Monoid a => Parser a -> Parser a
optempty = option mempty