All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sangria.parser.QueryParser.scala Maven / Gradle / Ivy

There is a newer version: 2.0.1
Show newest version
package sangria.parser

import org.parboiled2._
import CharPredicate.{HexDigit, Digit19, AlphaNum}

import sangria.ast

import scala.util.{Success, Failure}

trait Tokens extends StringBuilding with PositionTracking { this: Parser with Ignored ⇒

  def Token =  rule { Punctuator | Name | NumberValue | StringValue }

  val PunctuatorChar = CharPredicate("!$():=@[]{|}")

  def Punctuator = rule { PunctuatorChar | Ellipsis }

  def Ellipsis = rule { 3 times '.' }

  val NameFirstChar = CharPredicate.Alpha ++ '_'

  val NameChar = NameFirstChar ++ CharPredicate.Digit

  def Name = rule { capture(NameFirstChar ~ NameChar.*) ~ Ignored.* }

  def NumberValue = rule { trackPos ~ IntegerValuePart ~ FloatValuePart.? ~ Ignored.* ~>
      ((pos, intPart, floatPart) ⇒
        floatPart map (f ⇒ ast.BigDecimalValue(BigDecimal(intPart + f), Some(pos))) getOrElse
          ast.BigIntValue(BigInt(intPart), Some(pos))) }

  def FloatValuePart = rule { capture(FractionalPart ~ ExponentPart.? | ExponentPart) }

  def FractionalPart = rule { '.' ~ Digit.+ }

  def IntegerValuePart = rule { capture(NegativeSign.? ~ IntegerPart) }

  def IntegerPart = rule { ch('0') | NonZeroDigit ~ Digit.* }

  def ExponentPart = rule { ExponentIndicator ~ Sign.? ~ Digit.+ }

  def ExponentIndicator = rule { ch('e') | ch('E') }

  def Sign = rule { ch('-') | '+' }

  val NegativeSign  = '-'

  val NonZeroDigit = Digit19

  def Digit = rule { ch('0') | NonZeroDigit }

  def StringValue = rule { trackPos ~ '"' ~ clearSB() ~ Characters ~ '"' ~ push(sb.toString) ~ Ignored.* ~> ((pos, s) ⇒ ast.StringValue(s, Some(pos)))}

  def Characters = rule { (NormalChar | '\\' ~ EscapedChar).* }

  val QuoteBackslash = CharPredicate("\"\\")

  def NormalChar = rule { !(QuoteBackslash | LineTerminator) ~ ANY ~ appendSB() }

  def EscapedChar = rule {
    QuoteBackslash ~ appendSB() |
    'b' ~ appendSB('\b') |
    'f' ~ appendSB('\f') |
    'n' ~ appendSB('\n') |
    'r' ~ appendSB('\r') |
    't' ~ appendSB('\t') |
    Unicode ~> { code ⇒ sb.append(code.asInstanceOf[Char]); () }
  }

  def Unicode = rule { 'u' ~ capture(4 times HexDigit) ~> (Integer.parseInt(_, 16)) }

  def Keyword(s: String) = rule { s ~ !NameChar ~ Ignored.* }
}

trait Ignored extends PositionTracking { this: Parser ⇒

  val WhiteSpace = CharPredicate("\u0009\u0020")

  def CRLF = rule { '\u000D' ~ '\u000A' }

  val LineTerminator = CharPredicate("\u000A")

  val UnicodeBOM = CharPredicate('\uFEFF')

  def Ignored = rule { quiet(UnicodeBOM | WhiteSpace | (CRLF | LineTerminator) ~ trackNewLine | Comment | ',') }

  def Comment = rule { "#" ~ CommentChar.* }

  def CommentChar = rule { !LineTerminator ~ ANY }

  def ws(char: Char): Rule0 = rule { ch(char) ~ Ignored.* }

  def ws(s: String): Rule0 = rule { str(s) ~ Ignored.* }

}

trait Document { this: Parser with Operations with Ignored with Fragments with Operations with Values ⇒

  def Document = rule { Ignored.* ~ trackPos ~ Definition.+ ~ EOI ~> ((pos, d) ⇒ ast.Document(d.toList, Some(pos))) }

  def InputDocument = rule { Ignored.* ~ ValueConst ~ EOI }

  def Definition = rule { OperationDefinition | FragmentDefinition }

}

trait Operations extends PositionTracking { this: Parser with Tokens with Ignored with Fragments with Values with Types with Directives ⇒

  def OperationDefinition = rule {
    trackPos ~ SelectionSet ~> ((pos, s) ⇒ ast.OperationDefinition(selections = s, position = Some(pos))) |
    trackPos ~ OperationType ~ OperationName.? ~ (VariableDefinitions.? ~> (_ getOrElse Nil)) ~ (Directives.? ~> (_ getOrElse Nil)) ~ SelectionSet ~>
        ((pos, opType, name, vars, dirs, sels) ⇒ ast.OperationDefinition(opType, name, vars, dirs, sels, Some(pos)))
  }

  def OperationName = rule { Name }

  def OperationType = rule {
    Query ~ push(ast.OperationType.Query) |
    Mutation ~ push(ast.OperationType.Mutation) |
    Subscription ~ push(ast.OperationType.Subscription)
  }

  def Query = rule { Keyword("query") }

  def Mutation = rule { Keyword("mutation") }

  def Subscription = rule { Keyword("subscription") }

  def VariableDefinitions = rule { ws('(') ~ VariableDefinition.+ ~ ws(')') ~> (_.toList)}

  def VariableDefinition = rule { trackPos ~ Variable ~ ws(':') ~ Type ~ DefaultValue.? ~>
      ((pos, name, tpe, defaultValue) ⇒ ast.VariableDefinition(name, tpe, defaultValue, Some(pos))) }

  def Variable = rule { '$' ~ Name }

  def DefaultValue = rule { ws('=') ~ ValueConst }

  def SelectionSet: Rule1[List[ast.Selection]] = rule { ws('{') ~ Selection.+ ~ ws('}') ~> ((x: Seq[ast.Selection]) ⇒ x.toList) }

  def Selection = rule { Field | FragmentSpread | InlineFragment }

  def Field = rule { trackPos ~ Alias.? ~ Name ~
      (Arguments.? ~> (_ getOrElse Nil)) ~
      (Directives.? ~> (_ getOrElse Nil)) ~
      (SelectionSet.? ~> (_ getOrElse Nil)) ~>
        ((pos, alias, name, args, dirs, sels) ⇒ ast.Field(alias, name, args, dirs, sels, Some(pos))) }

  def Alias = rule { Name ~ ws(':') }

  def Arguments = rule { ws('(') ~ Argument.+ ~ ws(')') ~> (_.toList) }

  def Argument = rule { trackPos ~ Name ~ ws(':') ~ Value ~> ((pos, name, value) ⇒ ast.Argument(name, value, Some(pos))) }

}

trait Fragments { this: Parser with Tokens with Ignored with Directives with Types with Operations ⇒

  def FragmentSpread = rule { trackPos ~ Ellipsis ~ Ignored.* ~ FragmentName ~ (Directives.? ~> (_ getOrElse Nil)) ~>
      ((pos, name, dirs) ⇒ ast.FragmentSpread(name, dirs, Some(pos))) }

  def InlineFragment = rule { trackPos ~ Ellipsis ~ Ignored.* ~ TypeCondition.? ~ (Directives.? ~> (_ getOrElse Nil)) ~ SelectionSet ~>
      ((pos, typeCondition, dirs, sels) ⇒ ast.InlineFragment(typeCondition, dirs, sels, Some(pos))) }

  def on = rule { Keyword("on") }

  def Fragment = rule { Keyword("fragment") }

  def FragmentDefinition = rule { trackPos ~ Fragment ~ FragmentName ~ TypeCondition ~ (Directives.?  ~> (_ getOrElse Nil)) ~ SelectionSet ~>
      ((pos, name, typeCondition, dirs, sels) ⇒ ast.FragmentDefinition(name, typeCondition, dirs, sels, Some(pos))) }

  def FragmentName = rule { !on ~ Name }

  def TypeCondition = rule { on ~ NamedType }

}

trait Values { this: Parser with Tokens with Ignored with Operations ⇒

  def ValueConst: Rule1[ast.Value] = rule {
    NumberValue | StringValue | BooleanValue | NullValue | EnumValue | ListValueConst | ObjectValueConst
  }

  def Value: Rule1[ast.Value] = rule {
    trackPos ~ Variable ~> ((pos, name) ⇒ ast.VariableValue(name, Some(pos))) |
    NumberValue |
    StringValue |
    BooleanValue |
    NullValue |
    EnumValue |
    ListValue |
    ObjectValue
  }

  def BooleanValue = rule {
    trackPos ~ True ~> (pos ⇒ ast.BooleanValue(true, Some(pos))) |
    trackPos ~ False ~> (pos ⇒ ast.BooleanValue(false, Some(pos)))
  }

  def True = rule { Keyword("true") }

  def False = rule { Keyword("false") }

  def Null = rule { Keyword("null") }

  def NullValue = rule { trackPos ~ Null ~> (pos ⇒ ast.NullValue(Some(pos))) }

  def EnumValue = rule { !True ~ !False ~ trackPos ~ Name ~> ((pos, name) ⇒ ast.EnumValue(name, Some(pos))) }

  def ListValueConst = rule { trackPos ~ ws('[') ~ ValueConst.* ~ ws(']')  ~> ((pos, v) ⇒ ast.ListValue(v.toList, Some(pos))) }

  def ListValue = rule { trackPos ~ ws('[') ~ Value.* ~ ws(']') ~> ((pos, v) ⇒ ast.ListValue(v.toList, Some(pos))) }

  def ObjectValueConst = rule { trackPos ~ ws('{') ~ ObjectFieldConst.* ~ ws('}') ~> ((pos, f) ⇒ ast.ObjectValue(f.toList, Some(pos))) }

  def ObjectValue = rule { trackPos ~ ws('{') ~ ObjectField.* ~ ws('}') ~> ((pos, f) ⇒ ast.ObjectValue(f.toList, Some(pos))) }

  def ObjectFieldConst = rule { trackPos ~ Name ~ ws(':') ~ ValueConst ~> ((pos, name, value) ⇒ ast.ObjectField(name, value, Some(pos))) }

  def ObjectField = rule { trackPos ~ Name ~ ws(':') ~ Value ~> ((pos, name, value) ⇒ ast.ObjectField(name, value, Some(pos))) }

}

trait Directives { this: Parser with Tokens with Operations ⇒

  def Directives = rule { Directive.+ ~> (_.toList) }

  def Directive = rule { trackPos ~ '@' ~ Name ~ (Arguments.? ~> (_ getOrElse Nil)) ~>
      ((pos, name, args) ⇒ ast.Directive(name, args, Some(pos))) }

}

trait Types { this: Parser with Tokens with Ignored ⇒
  def Type: Rule1[ast.Type] = rule { NonNullType | ListType | NamedType }

  def TypeName = rule { Name }

  def NamedType = rule { trackPos ~ TypeName ~> ((pos, name) ⇒ ast.NamedType(name, Some(pos)))}

  def ListType = rule { trackPos ~ ws('[') ~ Type ~ ws(']') ~> ((pos, tpe) ⇒ ast.ListType(tpe, Some(pos))) }

  def NonNullType = rule {
    trackPos ~ TypeName ~ ws('!')  ~> ((pos, name) ⇒ ast.NotNullType(ast.NamedType(name, Some(pos)), Some(pos))) |
    trackPos ~ ListType ~ ws('!') ~> ((pos, tpe) ⇒ ast.NotNullType(tpe, Some(pos)))
  }
}

class QueryParser private (val input: ParserInput) 
    extends Parser with Tokens with Ignored with Document with Operations with Fragments with Values with Directives with Types

object QueryParser {
  def parse(input: String)(implicit scheme: DeliveryScheme[ast.Document]): scheme.Result =
    parse(ParserInput(input))(scheme)

  def parse(input: ParserInput)(implicit scheme: DeliveryScheme[ast.Document]): scheme.Result = {
    val parser = new QueryParser(input)

    parser.Document.run() match {
      case Success(res) ⇒
        scheme.success(res.copy(sourceMapper = Some(new Parboiled2SourceMapper(input))))
      case Failure(e: ParseError) ⇒ scheme.failure(SyntaxError(parser, input, e))
      case Failure(e) ⇒ scheme.failure(e)
    }
  }

  def parseInput(input: String)(implicit scheme: DeliveryScheme[ast.Value]): scheme.Result =
    parseInput(ParserInput(input))( scheme)

  def parseInput(input: ParserInput)(implicit scheme: DeliveryScheme[ast.Value]): scheme.Result = {
    val parser = new QueryParser(input)

    parser.InputDocument.run() match {
      case Success(res) ⇒ scheme.success(res)
      case Failure(e: ParseError) ⇒ scheme.failure(SyntaxError(parser, input, e))
      case Failure(e) ⇒ scheme.failure(e)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy