Current section
Files
Jump to
Current section
Files
lib/makeup_graphql.ex
defmodule MakeupGraphql do
@moduledoc """
A `Makeup` lexer for GraphQL.
"""
import Makeup.Lexer.Combinators
import Makeup.Lexer.Groups
import MakeupGraphql.Helpers
import NimbleParsec
@behaviour Makeup.Lexer
@impl Makeup.Lexer
def lex(text, opts \\ []) do
group_prefix = Keyword.get(opts, :group_prefix, random_prefix(10))
{:ok, tokens, "", _, _, _} = root(text)
tokens
|> postprocess([])
|> match_groups(group_prefix)
end
@impl Makeup.Lexer
def postprocess(tokens, _opts \\ []), do: tokens
@impl Makeup.Lexer
defgroupmatcher(:match_groups,
parentheses: [
open: [[{:punctuation, %{language: :graphql}, "("}]],
close: [[{:punctuation, %{language: :graphql}, ")"}]]
],
list: [
open: [
[{:punctuation, %{language: :graphql}, "["}]
],
close: [
[{:punctuation, %{language: :graphql}, "]"}]
]
],
curly: [
open: [
[{:punctuation, %{language: :graphql}, "{"}]
],
close: [
[{:punctuation, %{language: :graphql}, "}"}]
]
]
)
exclamation = ascii_char([?!]) |> ignore_whitespace(:punctuation)
dollar = ascii_char([?$]) |> ignore_whitespace(:punctuation)
open_paren = ascii_char([?(]) |> ignore_whitespace(:punctuation)
close_paren = ascii_char([?)]) |> ignore_whitespace(:punctuation)
colon = ascii_char([?:]) |> ignore_whitespace(:punctuation)
_equals = ascii_char([?=]) |> ignore_whitespace(:punctuation)
at = ascii_char([?@]) |> ignore_whitespace(:punctuation)
open_square = ascii_char([?[]) |> ignore_whitespace(:punctuation)
close_square = ascii_char([?]]) |> ignore_whitespace(:punctuation)
open_bracket = ascii_char([?{]) |> ignore_whitespace(:punctuation)
close_bracket = ascii_char([?}]) |> ignore_whitespace(:punctuation)
_pipe = ascii_char([?|]) |> ignore_whitespace(:punctuation)
negative_sign = ascii_char([?-])
digit = ascii_char([?0..?9])
non_zero_digit = ascii_char([?1..?9])
integer_part =
optional(negative_sign)
|> choice([
ascii_char([?0]),
non_zero_digit |> repeat(digit)
])
int_value = ignore_whitespace(integer_part, :number_integer)
fractional_part =
ascii_char([?.])
|> times(digit, min: 1)
exponent_indicator = ascii_char([?e, ?E])
sign = ascii_char([?+, ?-])
exponent_part =
exponent_indicator
|> optional(sign)
|> times(digit, min: 1)
float_value =
choice([
integer_part |> concat(fractional_part) |> concat(exponent_part),
integer_part |> post_traverse(:fill_mantissa) |> concat(exponent_part),
integer_part |> concat(fractional_part)
])
|> ignore_whitespace(:number_float)
unicode_char_in_string =
string("\\u")
|> ascii_string([?0..?9, ?a..?f, ?A..?F], 4)
|> token(:string_escape)
escaped_char =
string("\\")
|> utf8_string([], 1)
|> token(:string_escape)
combinators_inside_string = [
unicode_char_in_string,
escaped_char
]
single_string_value = string_like("\"", "\"", combinators_inside_string, :string)
block_string_value = string_like(~S["""], ~S["""], combinators_inside_string, :string)
string_value = choice([single_string_value, block_string_value]) |> ignore_whitespace()
operation_type = choice([keyword("query"), keyword("mutation"), keyword("subscription")])
boolean_value = choice([constant("true"), constant("false")])
alias_ = name() |> ascii_char([?:])
variable = dollar |> concat(symbol())
null_value = constant("null")
enum_value = lookahead_not(choice([boolean_value, null_value])) |> concat(class())
list_value = many_surrounded_by(parsec(:value), open_square, close_square)
object_field = symbol() |> concat(colon) |> parsec(:value)
object_value = many_surrounded_by(object_field, open_bracket, close_bracket)
defcombinatorp(
:value,
choice([
variable,
int_value,
float_value,
string_value,
boolean_value,
null_value,
enum_value,
list_value,
object_value
])
)
list_value_const = many_surrounded_by(parsec(:value_const), open_square, close_square)
object_field_const = symbol() |> concat(colon) |> parsec(:value_const)
object_value_const = many_surrounded_by(object_field_const, open_bracket, close_bracket)
defcombinatorp(
:value_const,
choice([
int_value,
float_value,
string_value,
boolean_value,
null_value,
enum_value,
list_value_const,
object_value_const
])
)
argument = symbol() |> concat(colon) |> parsec(:value)
arguments = many_surrounded_by(argument, open_paren, close_paren)
directive = at |> concat(func()) |> optional(arguments)
directives = directive |> repeat(directive)
fragment_name = lookahead_not(keyword("on")) |> concat(name())
fragment_spread =
string("...") |> token(:punctuation) |> concat(fragment_name) |> optional(directives)
named_type = class()
list_type = many_surrounded_by(parsec(:type), open_square, close_square)
non_null_type =
choice([
named_type |> concat(exclamation),
list_type |> concat(exclamation)
])
defcombinatorp(
:type,
choice([
non_null_type,
named_type,
list_type
])
)
default_value = parsec(:value_const)
variable_definition =
variable |> concat(colon) |> concat(parsec(:type)) |> optional(default_value)
variable_definitions = many_surrounded_by(variable_definition, open_paren, close_paren)
selection_set = many_surrounded_by(parsec(:selection), open_bracket, close_bracket)
type_condition = keyword("on") |> concat(named_type)
fragment =
string("fragment")
|> concat(fragment_name)
|> concat(type_condition)
|> optional(directives)
|> concat(selection_set)
inline_fragment =
string("...")
|> token(:punctuation)
|> optional(type_condition)
|> optional(directives)
|> concat(selection_set)
field =
optional(alias_)
|> concat(name())
|> optional(arguments)
|> optional(directives)
|> optional(selection_set)
defcombinatorp(
:selection,
choice([
field,
fragment_spread,
inline_fragment
])
)
operation =
choice([
operation_type
|> optional(func())
|> optional(variable_definitions)
|> optional(directives)
|> concat(selection_set),
selection_set
])
executable = choice([operation, fragment])
definition = executable
# choice([
# executable
# type_system_definition,
# type_system_extension
# ])
document = repeat(ignored()) |> concat(definition) |> repeat(definition)
defparsec(
:root,
document |> map(:as_graphql_language)
)
defparsec(
:root_element,
parsec(:root) |> post_traverse(:head)
)
defp as_graphql_language({ttype, meta, value}) do
{ttype, Map.put(meta, :language, :graphql), value}
end
defp head(_rest, [head | _], context, _line, _offset) do
{[head], context}
end
defp fill_mantissa(_rest, raw, context, _, _), do: {'0.' ++ raw, context}
end