///|
/// A recursive-descent GraphQL parser producing an executable `Document` AST. It
/// implements the executable half of the GraphQL grammar — operations
/// (query/mutation/subscription and the `{ ... }` shorthand), selection sets,
/// fields with aliases/arguments/directives, variable definitions with default
/// values, named and inline fragments, and directives — matching what
/// `graphql-core` (strawberry's parser) produces. Type-system (SDL) definitions
/// are handled by the code-first schema builder in `schema.mbt`.
///|
/// The parser holds a lexer and a one-token lookahead (`current`).
priv struct Parser {
lexer : Lexer
mut current : Token
}
///|
/// Create a parser over `source`, priming the first token.
fn Parser::new(source : String) -> Parser raise GqlSyntaxError {
let lexer = Lexer::new(source)
let current = lexer.next_token()
{ lexer, current }
}
///|
/// Consume and return the current token, advancing to the next.
fn Parser::advance(self : Parser) -> Token raise GqlSyntaxError {
let tok = self.current
self.current = self.lexer.next_token()
tok
}
///|
/// Whether the current token has kind `k`.
fn Parser::at(self : Parser, k : TokenKind) -> Bool {
self.current.kind == k
}
///|
/// Whether the current token is a `Name` with text `s` (a keyword check).
fn Parser::at_keyword(self : Parser, s : String) -> Bool {
self.current.kind is Name && self.current.value == s
}
///|
/// A human-readable description of a token kind, for error messages.
fn token_desc(k : TokenKind) -> String {
match k {
Name => "a name"
IntVal => "an int value"
FloatVal => "a float value"
StringVal => "a string value"
BlockStringVal => "a block string"
Bang => "'!'"
Dollar => "'$'"
Amp => "'&'"
ParenL => "'('"
ParenR => "')'"
Spread => "'...'"
Colon => "':'"
Equals => "'='"
At => "'@'"
BracketL => "'['"
BracketR => "']'"
BraceL => "'{'"
BraceR => "'}'"
Pipe => "'|'"
Eof => ""
}
}
///|
/// A short description of the current token, for "found X" diagnostics.
fn Parser::current_desc(self : Parser) -> String {
match self.current.kind {
Name => "name '" + self.current.value + "'"
IntVal | FloatVal => "value '" + self.current.value + "'"
StringVal | BlockStringVal => "a string"
other => token_desc(other)
}
}
///|
/// Raise a syntax error at the current token's position (expression position:
/// the polymorphic return lets it stand in for any value).
fn[T] Parser::fail(self : Parser, msg : String) -> T raise GqlSyntaxError {
raise GqlSyntaxError(msg, self.current.line, self.current.col)
}
///|
/// Raise a syntax error at the current token's position (statement position).
fn Parser::err(self : Parser, msg : String) -> Unit raise GqlSyntaxError {
raise GqlSyntaxError(msg, self.current.line, self.current.col)
}
///|
/// Consume a token of kind `k`, or raise "expected ..., found ...".
fn Parser::expect(self : Parser, k : TokenKind) -> Token raise GqlSyntaxError {
if self.at(k) {
self.advance()
} else {
self.fail("expected " + token_desc(k) + ", found " + self.current_desc())
}
}
///|
/// Consume the current `Name` token and return its text, or raise.
fn Parser::expect_name(self : Parser) -> String raise GqlSyntaxError {
self.expect(Name).value
}
///|
/// Consume a `Name` token equal to the keyword `kw`, or raise.
fn Parser::expect_keyword(
self : Parser,
kw : String,
) -> Unit raise GqlSyntaxError {
if self.at_keyword(kw) {
self.advance() |> ignore
} else {
self.fail("expected '" + kw + "', found " + self.current_desc())
}
}
///|
/// Parse a complete document: a sequence of executable definitions up to `Eof`.
fn Parser::parse_document(self : Parser) -> Document raise GqlSyntaxError {
let definitions = []
while not(self.at(Eof)) {
definitions.push(self.parse_definition())
}
if definitions.length() == 0 {
self.err("expected at least one definition")
}
{ definitions, }
}
///|
/// Local boolean negation used in loop guards.
fn not(b : Bool) -> Bool {
!b
}
///|
/// Parse one top-level definition: an operation (long form or `{ ... }`
/// shorthand) or a fragment definition.
fn Parser::parse_definition(self : Parser) -> Definition raise GqlSyntaxError {
if self.at(BraceL) {
return OperationDef(self.parse_operation())
}
if self.at_keyword("query") ||
self.at_keyword("mutation") ||
self.at_keyword("subscription") {
return OperationDef(self.parse_operation())
}
if self.at_keyword("fragment") {
return FragmentDef(self.parse_fragment())
}
self.fail(
"expected 'query', 'mutation', 'subscription', 'fragment' or '{', found " +
self.current_desc(),
)
}
///|
/// Parse an operation definition, including the anonymous query shorthand.
fn Parser::parse_operation(
self : Parser,
) -> OperationDefinition raise GqlSyntaxError {
if self.at(BraceL) {
let selection_set = self.parse_selection_set()
return {
operation: Query,
name: None,
variable_definitions: [],
directives: [],
selection_set,
}
}
let operation = match self.current.value {
"query" => Query
"mutation" => Mutation
_ => Subscription
}
self.advance() |> ignore
let name = if self.at(Name) { Some(self.advance().value) } else { None }
let variable_definitions = self.parse_variable_definitions()
let directives = self.parse_directives()
let selection_set = self.parse_selection_set()
{ operation, name, variable_definitions, directives, selection_set }
}
///|
/// Parse `($v: T = default @dir, ...)`, or an empty list when absent.
fn Parser::parse_variable_definitions(
self : Parser,
) -> Array[VariableDefinition] raise GqlSyntaxError {
let defs = []
if not(self.at(ParenL)) {
return defs
}
self.advance() |> ignore
while not(self.at(ParenR)) && not(self.at(Eof)) {
self.expect(Dollar) |> ignore
let variable = self.expect_name()
self.expect(Colon) |> ignore
let typ = self.parse_type()
let default_value = if self.at(Equals) {
self.advance() |> ignore
Some(self.parse_value(true))
} else {
None
}
let directives = self.parse_directives()
defs.push({ variable, typ, default_value, directives })
}
self.expect(ParenR) |> ignore
defs
}
///|
/// Parse a type reference: `Name`, `[Type]`, or either followed by `!`.
fn Parser::parse_type(self : Parser) -> TypeRef raise GqlSyntaxError {
let mut t = if self.at(BracketL) {
self.advance() |> ignore
let inner = self.parse_type()
self.expect(BracketR) |> ignore
ListType(inner)
} else {
NamedType(self.expect_name())
}
if self.at(Bang) {
self.advance() |> ignore
t = NonNullType(t)
}
t
}
///|
/// Parse zero or more `@name(args)` directives.
fn Parser::parse_directives(
self : Parser,
) -> Array[Directive] raise GqlSyntaxError {
let ds = []
while self.at(At) {
self.advance() |> ignore
let name = self.expect_name()
let arguments = self.parse_arguments(false)
ds.push({ name, arguments })
}
ds
}
///|
/// Parse `(name: value, ...)`, or an empty list when absent. `const` forbids
/// variables in the values (used for default values and directive args in
/// variable definitions).
fn Parser::parse_arguments(
self : Parser,
const_ : Bool,
) -> Array[Argument] raise GqlSyntaxError {
let args = []
if not(self.at(ParenL)) {
return args
}
self.advance() |> ignore
while not(self.at(ParenR)) && not(self.at(Eof)) {
let name = self.expect_name()
self.expect(Colon) |> ignore
let value = self.parse_value(const_)
args.push({ name, value })
}
self.expect(ParenR) |> ignore
args
}
///|
/// Parse a non-empty selection set `{ selection+ }`.
fn Parser::parse_selection_set(
self : Parser,
) -> Array[Selection] raise GqlSyntaxError {
self.expect(BraceL) |> ignore
let sels = []
while not(self.at(BraceR)) && not(self.at(Eof)) {
sels.push(self.parse_selection())
}
self.expect(BraceR) |> ignore
if sels.length() == 0 {
self.err("selection set must contain at least one selection")
}
sels
}
///|
/// Parse one selection: a field, a `...Name` fragment spread, or a
/// `... on Type { ... }` / `... { ... }` inline fragment.
fn Parser::parse_selection(self : Parser) -> Selection raise GqlSyntaxError {
if self.at(Spread) {
self.advance() |> ignore
if self.at_keyword("on") {
self.advance() |> ignore
let cond = self.expect_name()
let directives = self.parse_directives()
let sels = self.parse_selection_set()
return InlineFragmentSel(Some(cond), directives, sels)
}
if self.at(Name) {
let name = self.advance().value
let directives = self.parse_directives()
return FragmentSpreadSel(name, directives)
}
let directives = self.parse_directives()
let sels = self.parse_selection_set()
return InlineFragmentSel(None, directives, sels)
}
FieldSel(self.parse_field())
}
///|
/// Parse a field: `alias: name(args) @dir { ... }` (alias and the trailing
/// selection set are optional).
fn Parser::parse_field(self : Parser) -> QueryField raise GqlSyntaxError {
let first = self.expect_name()
let mut field_alias : String? = None
let mut name = first
if self.at(Colon) {
self.advance() |> ignore
field_alias = Some(first)
name = self.expect_name()
}
let arguments = self.parse_arguments(false)
let directives = self.parse_directives()
let selection_set = if self.at(BraceL) {
self.parse_selection_set()
} else {
[]
}
{ alias_: field_alias, name, arguments, directives, selection_set }
}
///|
/// Parse a fragment definition: `fragment Name on Type @dir { ... }`. The
/// fragment name must not be the reserved word `on`.
fn Parser::parse_fragment(
self : Parser,
) -> FragmentDefinition raise GqlSyntaxError {
self.expect_keyword("fragment")
if self.at_keyword("on") {
self.err("fragment name must not be 'on'")
}
let name = self.expect_name()
self.expect_keyword("on")
let type_condition = self.expect_name()
let directives = self.parse_directives()
let selection_set = self.parse_selection_set()
{ name, type_condition, directives, selection_set }
}
///|
/// Parse an input value. `const` (used for default values) forbids `$variable`.
fn Parser::parse_value(
self : Parser,
const_ : Bool,
) -> Value raise GqlSyntaxError {
match self.current.kind {
Dollar => {
if const_ {
self.err("variable is not allowed in a constant value")
}
self.advance() |> ignore
Variable(self.expect_name())
}
IntVal => IntValue(self.advance().value)
FloatVal => FloatValue(self.advance().value)
StringVal => StringValue(self.advance().value, false)
BlockStringVal => StringValue(self.advance().value, true)
Name =>
match self.current.value {
"true" => {
self.advance() |> ignore
BooleanValue(true)
}
"false" => {
self.advance() |> ignore
BooleanValue(false)
}
"null" => {
self.advance() |> ignore
NullValue
}
_ => EnumValue(self.advance().value)
}
BracketL => {
self.advance() |> ignore
let items = []
while not(self.at(BracketR)) && not(self.at(Eof)) {
items.push(self.parse_value(const_))
}
self.expect(BracketR) |> ignore
ListValue(items)
}
BraceL => {
self.advance() |> ignore
let fields = []
while not(self.at(BraceR)) && not(self.at(Eof)) {
let k = self.expect_name()
self.expect(Colon) |> ignore
let v = self.parse_value(const_)
fields.push((k, v))
}
self.expect(BraceR) |> ignore
ObjectValue(fields)
}
_ => self.fail("expected a value, found " + self.current_desc())
}
}
///|
/// Parse a GraphQL executable document from source text. This is the front half
/// of the executor: `parse(query).definitions` yields the operations and
/// fragments to validate and execute.
pub fn parse(source : String) -> Document raise GqlSyntaxError {
let p = Parser::new(source)
p.parse_document()
}