makeup-prisma/lib/makeup_prisma.ex
2024-10-17 10:56:53 +02:00

227 lines
5.1 KiB
Elixir

defmodule Makeup.Lexers.MakeupPrisma do
@moduledoc """
A `makeup` lexer for Prisma.
"""
import Makeup.Lexer.Combinators
import Makeup.Lexer.Groups
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: :prisma}, "("}]],
close: [[{:punctuation, %{language: :prisma}, ")"}]]
],
list: [
open: [
[{:punctuation, %{language: :prisma}, "["}]
],
close: [
[{:punctuation, %{language: :prisma}, "]"}]
]
],
curly: [
open: [
[{:punctuation, %{language: :prisma}, "{"}]
],
close: [
[{:punctuation, %{language: :prisma}, "}"}]
]
]
)
# Codepoints
@horizontal_tab 0x0009
@newline 0x000A
@carriage_return 0x000D
@space 0x0020
@unicode_bom 0xFEFF
any_unicode = utf8_char([])
unicode_bom = ignore(utf8_char([@unicode_bom]))
whitespace =
ascii_string(
[
@horizontal_tab,
@space
],
min: 1
)
|> token(:whitespace)
line_terminator =
choice([
ascii_char([@newline]),
ascii_char([@carriage_return])
|> optional(ascii_char([@newline]))
])
|> token(:whitespace)
comment =
string("//")
|> repeat_while(any_unicode, {:not_line_terminator, []})
|> token(:comment_single)
comma = ascii_char([?,]) |> token(:punctuation)
punctuator =
ascii_char([
?(,
?),
?:,
?=,
?@,
?[,
?],
?{,
?|,
?}
])
|> token(:punctuation)
boolean_value_or_name_or_reserved_word =
ascii_char([?_, ?A..?Z, ?a..?z])
|> repeat(ascii_char([?_, ?0..?9, ?A..?Z, ?a..?z]))
|> optional(ascii_char([??]))
|> post_traverse({:boolean_value_or_name_or_reserved_word, []})
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 =
empty()
|> concat(integer_part)
|> token(:number_integer)
fractional_part =
ascii_char([?.])
|> times(digit, min: 1)
float_value =
choice([
integer_part |> concat(fractional_part),
integer_part |> post_traverse({:fill_mantissa, []}),
integer_part |> concat(fractional_part)
])
|> token(: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
]
string_value = string_like("\"", "\"", combinators_inside_string, :string)
block_string_value = string_like(~S["""], ~S["""], combinators_inside_string, :string)
root_element_combinator =
choice([
unicode_bom,
whitespace,
line_terminator,
comment,
comma,
punctuator,
block_string_value,
string_value,
float_value,
int_value,
boolean_value_or_name_or_reserved_word
])
@doc false
def __as_prisma_language__({ttype, meta, value}) do
{ttype, Map.put(meta, :language, :prisma), value}
end
defparsec(
:root_element,
root_element_combinator |> map({__MODULE__, :__as_prisma_language__, []})
)
defparsec(
:root,
repeat(parsec(:root_element))
)
defp fill_mantissa(_rest, raw, context, _, _), do: {~c"0." ++ raw, context}
@boolean_words ~w(
true
false
) |> Enum.map(&String.to_charlist/1)
@reserved_words ~w(
enum
model
datasource
generator
) |> Enum.map(&String.to_charlist/1)
defp boolean_value_or_name_or_reserved_word(rest, chars, context, loc, byte_offset) do
value = chars |> Enum.reverse()
do_boolean_value_or_name_or_reserved_word(rest, value, context, loc, byte_offset)
end
defp do_boolean_value_or_name_or_reserved_word(_rest, value, context, _loc, _byte_offset)
when value in @boolean_words do
{[{:name_constant, %{}, value}], context}
end
defp do_boolean_value_or_name_or_reserved_word(_rest, value, context, _loc, _byte_offset)
when value in @reserved_words do
{[{:keyword_reserved, %{}, value}], context}
end
defp do_boolean_value_or_name_or_reserved_word(_rest, value, context, _loc, _byte_offset) do
{[{:name, %{}, value}], context}
end
def line_and_column({line, line_offset}, byte_offset, column_correction) do
column = byte_offset - line_offset - column_correction + 1
{line, column}
end
defp not_line_terminator(<<?\n, _::binary>>, context, _, _), do: {:halt, context}
defp not_line_terminator(<<?\r, _::binary>>, context, _, _), do: {:halt, context}
defp not_line_terminator(_, context, _, _), do: {:cont, context}
end