Current section

Files

Jump to
soulless lib soulless protobuf parser.ex
Raw

lib/soulless/protobuf/parser.ex

defmodule Soulless.Protobuf.Parser.Helpers do
import NimbleParsec
def white_space(combinator \\ empty()) do
ignore(combinator, ascii_string([?\s, ?\n, ?\t], min: 1))
end
def optional_white_space(combinator \\ empty()) do
optional(white_space(combinator))
end
def semicolon(combinator \\ empty()) do
ignore(combinator, string(";"))
end
def equals(combinator \\ empty()) do
ignore(combinator, string("="))
end
def legal_string(combinator \\ empty()) do
ascii_string(combinator, [?0..?9, ?a..?z, ?A..?Z, ?_, ?.], min: 1)
end
def literal_between(combinator \\ empty(), value, start_string, end_string) do
combinator
|> ignore(string(start_string))
|> string(value)
|> ignore(string(end_string))
end
def quoted_literal(combinator \\ empty(), value) do
literal_between(combinator, value, "\"", "\"")
end
def legal_string_between(combinator \\ empty(), start_string, end_string) do
combinator
|> ignore(string(start_string))
|> legal_string()
|> ignore(string(end_string))
end
def quoted_legal_string(combinator \\ empty()) do
legal_string_between(combinator, "\"", "\"")
end
def start_block(combinator \\ empty(), symbol \\ "{") do
ignore(combinator, string(symbol))
end
def end_block(combinator \\ empty(), symbol \\ "}") do
ignore(combinator, string(symbol))
end
def protobuf_type(combinator \\ empty()) do
combinator
|> choice([
string("any") |> replace(:any),
string("bool") |> replace(:bool),
string("string") |> replace(:string),
string("bytes") |> replace(:bytes),
string("int32") |> replace(:int32),
string("int64") |> replace(:int64),
string("uint32") |> replace(:uint32),
string("uint64") |> replace(:uint64),
string("sint32") |> replace(:sint32),
string("sint64") |> replace(:sint64),
string("fixed32") |> replace(:fixed32),
string("fixed64") |> replace(:fixed64),
string("sfixed32") |> replace(:sfixed32),
string("sfixed64") |> replace(:sfixed64),
string("float") |> replace(:float),
string("double") |> replace(:double),
legal_string() |> unwrap_and_tag(:custom)
])
end
def type_in_parentheses(combinator \\ empty()) do
combinator
|> start_block("(")
|> protobuf_type()
|> end_block(")")
end
end
defmodule Soulless.Protobuf.Parser do
import NimbleParsec
import Soulless.Protobuf.Parser.Helpers
defcombinatorp(
:syntax,
ignore(string("syntax"))
|> optional_white_space()
|> equals()
|> optional_white_space()
|> choice([
quoted_literal("proto2") |> replace(:proto2),
quoted_literal("proto3") |> replace(:proto3)
])
|> semicolon()
)
defcombinatorp(
:package,
ignore(string("package"))
|> white_space()
|> legal_string()
|> semicolon()
)
defcombinatorp(
:import,
ignore(string("import"))
|> white_space()
|> quoted_legal_string()
|> semicolon()
)
defcombinatorp(
:enum_field,
empty()
|> concat(
legal_string()
|> unwrap_and_tag(:name)
)
|> optional_white_space()
|> equals()
|> optional_white_space()
|> concat(integer(min: 1) |> unwrap_and_tag(:value))
|> semicolon()
)
defcombinatorp(
:enum,
ignore(string("enum"))
|> white_space()
|> concat(legal_string() |> unwrap_and_tag(:name))
|> optional_white_space()
|> start_block()
|> concat(
repeat(optional_white_space() |> parsec(:enum_field) |> tag(:option))
|> tag(:options)
)
|> optional_white_space()
|> end_block()
)
defcombinator(
:field,
empty()
|> concat(
protobuf_type()
|> unwrap_and_tag(:type)
)
|> white_space()
|> concat(
legal_string()
|> unwrap_and_tag(:name)
)
|> white_space()
|> equals()
|> white_space()
|> concat(integer(min: 1) |> unwrap_and_tag(:id))
|> semicolon()
)
defcombinatorp(
:repeated_field,
ignore(string("repeated"))
|> white_space()
|> parsec(:field)
)
defcombinatorp(
:optional_field,
ignore(string("optional"))
|> white_space()
|> parsec(:field)
)
defcombinatorp(
:field_or_message,
choice([
parsec(:repeated_field) |> tag(:repeated_field),
parsec(:optional_field) |> tag(:optional_field),
parsec(:field) |> tag(:field),
lookahead(string("message")) |> parsec(:message) |> tag(:message)
])
)
defcombinatorp(
:message,
ignore(string("message"))
|> white_space()
|> concat(legal_string() |> unwrap_and_tag(:name))
|> white_space()
|> start_block()
|> concat(
repeat(optional_white_space() |> parsec(:field_or_message))
|> tag(:fields)
)
|> optional_white_space()
|> end_block()
)
defcombinatorp(
:rpc,
ignore(string("rpc"))
|> white_space()
|> concat(legal_string() |> unwrap_and_tag(:function))
|> white_space()
|> concat(type_in_parentheses() |> unwrap_and_tag(:request_type))
|> white_space()
|> ignore(string("returns"))
|> white_space()
|> concat(type_in_parentheses() |> unwrap_and_tag(:return_type))
|> semicolon()
)
defcombinatorp(
:service,
ignore(string("service"))
|> white_space()
|> concat(legal_string() |> unwrap_and_tag(:name))
|> white_space()
|> start_block()
|> concat(
repeat(optional_white_space() |> parsec(:rpc) |> tag(:rpc))
|> tag(:procedures)
)
|> optional_white_space()
|> end_block()
)
defcombinatorp(
:definition,
choice([
lookahead(string("message")) |> parsec(:message) |> tag(:message),
lookahead(string("service")) |> parsec(:service) |> tag(:service),
lookahead(string("enum")) |> parsec(:enum) |> tag(:enum),
lookahead(string("import")) |> parsec(:import) |> tag(:import),
])
)
defcombinatorp(
:proto,
optional_white_space()
|> concat(parsec(:syntax) |> unwrap_and_tag(:syntax))
|> optional_white_space()
|> concat(parsec(:package) |> unwrap_and_tag(:package))
|> repeat(optional_white_space() |> parsec(:definition))
|> optional_white_space()
|> eos()
)
defparsec(:parse, parsec(:proto))
end