Packages

A Protocol Buffers library for Gleam, providing encoding and decoding of protobuf messages.

Current section

Files

Jump to
protozoa src protozoa@parser.erl
Raw

src/protozoa@parser.erl

-module(protozoa@parser).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/protozoa/parser.gleam").
-export([describe_parse_error/1, parse/1]).
-export_type([path/0, parse_error/0, proto_type/0, field_option/0, field/0, enum_value/0, enum/0, oneof/0, message/0, import/0, method/0, service/0, proto_file/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Protocol Buffer Parser Module\n"
"\n"
" This module provides parsing functionality for Protocol Buffer (.proto) files.\n"
" It transforms proto3 syntax text into structured Gleam data types that can be\n"
" used for code generation and type analysis.\n"
"\n"
" ## Capabilities\n"
"\n"
" - **Full proto3 syntax support**: Messages, enums, services, fields, imports, packages\n"
" - **Nested structures**: Supports nested messages and enums within messages \n"
" - **Advanced features**: Oneofs, maps, repeated fields, optional fields, field options\n"
" - **Service definitions**: Parses RPC services with method definitions and streaming support\n"
" - **Import handling**: Parses import statements (public, weak, regular)\n"
" - **Robust parsing**: Handles comments, whitespace, and malformed input gracefully\n"
" - **Type definitions**: Comprehensive type system covering all proto3 types\n"
"\n"
" ## Main Function\n"
"\n"
" The primary entry point is `parse()` which takes raw proto file content as a string\n"
" and returns a structured `ProtoFile` representation. All other types in this module\n"
" are internal data structures used to represent the parsed content.\n"
"\n"
" ## Proto3 Support\n"
"\n"
" Supported proto3 features:\n"
" - Messages with fields (scalar types, message types, enums)\n"
" - Nested messages and enums \n"
" - Oneof groups for union types\n"
" - Repeated fields for arrays/lists\n"
" - Map fields for key-value pairs\n"
" - Service definitions with RPC methods\n"
" - Streaming RPC support (client, server, bidirectional)\n"
" - Field options (deprecated, json_name, packed)\n"
" - Import statements with search path resolution\n"
" - Package declarations for namespacing\n"
" - Field numbers and naming\n"
"\n"
" ## Limitations\n"
"\n"
" - Only proto3 syntax (no proto2)\n"
" - No service definitions (RPC) \n"
" - Basic comment handling (strips // comments)\n"
" - Limited validation (focuses on structure over semantics)\n"
).
-type path() :: {path, binary(), proto_file()}.
-type parse_error() :: {invalid_syntax, binary(), binary()} |
{missing_required_syntax, binary()} |
{invalid_field_number, binary(), binary()} |
{duplicate_field_number, binary(), integer()} |
{malformed_field, binary()} |
{malformed_message, binary()} |
{malformed_enum, binary()} |
{invalid_map_type, binary()} |
{empty_message, binary()}.
-type proto_type() :: double |
float |
int32 |
int64 |
u_int32 |
u_int64 |
s_int32 |
s_int64 |
fixed32 |
fixed64 |
s_fixed32 |
s_fixed64 |
bool |
string |
bytes |
{message_type, binary()} |
{enum_type, binary()} |
{repeated, proto_type()} |
{optional, proto_type()} |
{map, proto_type(), proto_type()}.
-type field_option() :: {deprecated, boolean()} |
{json_name, binary()} |
{packed, boolean()}.
-type field() :: {field,
binary(),
proto_type(),
integer(),
gleam@option:option(binary()),
list(field_option())}.
-type enum_value() :: {enum_value, binary(), integer()}.
-type enum() :: {enum, binary(), list(enum_value())}.
-type oneof() :: {oneof, binary(), list(field())}.
-type message() :: {message,
binary(),
list(field()),
list(oneof()),
list(message()),
list(enum())}.
-type import() :: {import, binary(), boolean(), boolean()}.
-type method() :: {method, binary(), binary(), binary(), boolean(), boolean()}.
-type service() :: {service, binary(), list(method())}.
-type proto_file() :: {proto_file,
binary(),
gleam@option:option(binary()),
list(import()),
list(message()),
list(enum()),
list(service())}.
-file("src/protozoa/parser.gleam", 68).
-spec describe_parse_error(parse_error()) -> binary().
describe_parse_error(Error) ->
case Error of
{invalid_syntax, Line, Reason} ->
<<<<<<"Invalid syntax on line '"/utf8, Line/binary>>/binary,
"': "/utf8>>/binary,
Reason/binary>>;
{missing_required_syntax, Element} ->
<<<<"Missing required "/utf8, Element/binary>>/binary,
" declaration"/utf8>>;
{invalid_field_number, Field, Number} ->
<<<<<<<<"Invalid field number '"/utf8, Number/binary>>/binary,
"' for field '"/utf8>>/binary,
Field/binary>>/binary,
"'. Field numbers must be positive integers."/utf8>>;
{duplicate_field_number, Message, Number@1} ->
<<<<<<<<"Duplicate field number "/utf8,
(erlang:integer_to_binary(Number@1))/binary>>/binary,
" in message '"/utf8>>/binary,
Message/binary>>/binary,
"'"/utf8>>;
{malformed_field, Line@1} ->
<<<<"Malformed field definition: '"/utf8, Line@1/binary>>/binary,
"'"/utf8>>;
{malformed_message, Line@2} ->
<<<<"Malformed message definition: '"/utf8, Line@2/binary>>/binary,
"'"/utf8>>;
{malformed_enum, Line@3} ->
<<<<"Malformed enum definition: '"/utf8, Line@3/binary>>/binary,
"'"/utf8>>;
{invalid_map_type, Line@4} ->
<<<<"Invalid map type definition: '"/utf8, Line@4/binary>>/binary,
"'"/utf8>>;
{empty_message, Name} ->
<<<<"Message '"/utf8, Name/binary>>/binary,
"' is empty and has no fields"/utf8>>
end.
-file("src/protozoa/parser.gleam", 247).
-spec find_imports(list(binary())) -> {ok, list(import())} |
{error, parse_error()}.
find_imports(Lines) ->
Import_lines = gleam@list:filter(
Lines,
fun(Line) ->
Trimmed = gleam@string:trim(Line),
gleam_stdlib:string_starts_with(Trimmed, <<"import "/utf8>>)
end
),
gleam@list:try_map(
Import_lines,
fun(Line@1) ->
Trimmed@1 = gleam@string:trim(Line@1),
Clean_line = case gleam@string:split(Trimmed@1, <<"//"/utf8>>) of
[Main | _] ->
Main;
[] ->
Trimmed@1
end,
case gleam_stdlib:contains_string(Clean_line, <<"\""/utf8>>) of
false ->
{error,
{invalid_syntax,
Line@1,
<<"import statement must contain quoted path"/utf8>>}};
true ->
Without_import = gleam@string:replace(
Clean_line,
<<"import "/utf8>>,
<<""/utf8>>
),
Public = gleam_stdlib:string_starts_with(
Without_import,
<<"public "/utf8>>
),
Weak = gleam_stdlib:string_starts_with(
Without_import,
<<"weak "/utf8>>
),
Path_part = begin
_pipe = Without_import,
_pipe@1 = gleam@string:replace(
_pipe,
<<"public "/utf8>>,
<<""/utf8>>
),
_pipe@2 = gleam@string:replace(
_pipe@1,
<<"weak "/utf8>>,
<<""/utf8>>
),
gleam@string:trim(_pipe@2)
end,
case gleam@string:split(Path_part, <<"\""/utf8>>) of
[_, Path | _] ->
{ok, {import, Path, Public, Weak}};
_ ->
{error,
{invalid_syntax,
Line@1,
<<"invalid import path syntax"/utf8>>}}
end
end
end
).
-file("src/protozoa/parser.gleam", 288).
-spec find_syntax(list(binary())) -> {ok, binary()} | {error, parse_error()}.
find_syntax(Lines) ->
case gleam@list:find(
Lines,
fun(Line) ->
gleam_stdlib:string_starts_with(Line, <<"syntax"/utf8>>)
end
) of
{ok, Line@1} ->
case gleam_stdlib:contains_string(Line@1, <<"proto3"/utf8>>) of
true ->
{ok, <<"proto3"/utf8>>};
false ->
case gleam_stdlib:contains_string(Line@1, <<"proto2"/utf8>>) of
true ->
{error,
{invalid_syntax,
Line@1,
<<"proto2 syntax not supported, only proto3"/utf8>>}};
false ->
{error,
{invalid_syntax,
Line@1,
<<"invalid syntax declaration"/utf8>>}}
end
end;
{error, _} ->
{error, {missing_required_syntax, <<"syntax"/utf8>>}}
end.
-file("src/protozoa/parser.gleam", 308).
-spec find_package(list(binary())) -> gleam@option:option(binary()).
find_package(Lines) ->
case gleam@list:find(
Lines,
fun(Line) ->
gleam_stdlib:string_starts_with(Line, <<"package"/utf8>>)
end
) of
{ok, Line@1} ->
{some,
begin
_pipe = Line@1,
_pipe@1 = gleam@string:replace(
_pipe,
<<"package "/utf8>>,
<<""/utf8>>
),
_pipe@2 = gleam@string:replace(
_pipe@1,
<<";"/utf8>>,
<<""/utf8>>
),
gleam@string:trim(_pipe@2)
end};
{error, _} ->
none
end.
-file("src/protozoa/parser.gleam", 417).
-spec has_duplicates_helper(list(integer()), gleam@set:set(integer())) -> gleam@option:option(integer()).
has_duplicates_helper(Numbers, Seen) ->
case Numbers of
[] ->
none;
[Num | Rest] ->
case gleam@set:contains(Seen, Num) of
true ->
{some, Num};
false ->
has_duplicates_helper(Rest, gleam@set:insert(Seen, Num))
end
end.
-file("src/protozoa/parser.gleam", 413).
-spec has_duplicates(list(integer())) -> gleam@option:option(integer()).
has_duplicates(Numbers) ->
has_duplicates_helper(Numbers, gleam@set:new()).
-file("src/protozoa/parser.gleam", 388).
-spec validate_messages(list(message())) -> {ok, list(message())} |
{error, parse_error()}.
validate_messages(Messages) ->
gleam@list:try_map(
Messages,
fun(Msg) ->
All_field_numbers = gleam@list:map(
erlang:element(3, Msg),
fun(Field) -> erlang:element(4, Field) end
),
case has_duplicates(All_field_numbers) of
{some, Dup_num} ->
{error,
{duplicate_field_number,
erlang:element(2, Msg),
Dup_num}};
none ->
case gleam@list:find(
erlang:element(3, Msg),
fun(Field@1) -> erlang:element(4, Field@1) =< 0 end
) of
{ok, Invalid_field} ->
{error,
{invalid_field_number,
erlang:element(2, Invalid_field),
erlang:integer_to_binary(
erlang:element(4, Invalid_field)
)}};
{error, _} ->
{ok, Msg}
end
end
end
).
-file("src/protozoa/parser.gleam", 492).
-spec extract_body(list(binary()), list(binary()), integer()) -> {list(binary()),
list(binary())}.
extract_body(Lines, Body, Depth) ->
case Lines of
[] ->
{lists:reverse(Body), []};
[Line | Rest] ->
Trimmed = gleam@string:trim(Line),
case gleam_stdlib:contains_string(Trimmed, <<"}"/utf8>>) andalso not gleam_stdlib:contains_string(
Trimmed,
<<"{"/utf8>>
) of
true ->
case Depth of
0 ->
{lists:reverse(Body), Rest};
_ ->
extract_body(Rest, [Line | Body], Depth - 1)
end;
false ->
New_depth = case gleam_stdlib:contains_string(
Trimmed,
<<"{"/utf8>>
) of
true ->
Depth + 1;
false ->
Depth
end,
extract_body(Rest, [Line | Body], New_depth)
end
end.
-file("src/protozoa/parser.gleam", 812).
-spec parse_single_option(binary()) -> {ok, field_option()} | {error, nil}.
parse_single_option(Option_str) ->
case gleam@string:split(Option_str, <<"="/utf8>>) of
[<<"deprecated"/utf8>>, <<"true"/utf8>>] ->
{ok, {deprecated, true}};
[<<"deprecated"/utf8>>, <<"false"/utf8>>] ->
{ok, {deprecated, false}};
[<<"packed"/utf8>>, <<"true"/utf8>>] ->
{ok, {packed, true}};
[<<"packed"/utf8>>, <<"false"/utf8>>] ->
{ok, {packed, false}};
[<<"json_name"/utf8>>, Value] ->
Clean_value = case gleam_stdlib:string_starts_with(
Value,
<<"\""/utf8>>
)
andalso gleam_stdlib:string_ends_with(Value, <<"\""/utf8>>) of
true ->
_pipe = Value,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
gleam@string:drop_end(_pipe@1, 1);
false ->
Value
end,
{ok, {json_name, Clean_value}};
_ ->
{error, nil}
end.
-file("src/protozoa/parser.gleam", 805).
-spec parse_option_list(binary()) -> list(field_option()).
parse_option_list(Content) ->
_pipe = Content,
_pipe@1 = gleam@string:split(_pipe, <<","/utf8>>),
_pipe@2 = gleam@list:map(_pipe@1, fun gleam@string:trim/1),
gleam@list:filter_map(_pipe@2, fun parse_single_option/1).
-file("src/protozoa/parser.gleam", 782).
?DOC(" Parse field options from a string like \"[deprecated=true, json_name=\"myfield\"]\"\n").
-spec parse_field_options(binary()) -> list(field_option()).
parse_field_options(Options_str) ->
Trimmed = gleam@string:trim(Options_str),
case Trimmed of
<<""/utf8>> ->
[];
_ ->
case gleam_stdlib:string_starts_with(Trimmed, <<"["/utf8>>) andalso gleam_stdlib:string_ends_with(
Trimmed,
<<"]"/utf8>>
) of
true ->
Content = begin
_pipe = Trimmed,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
_pipe@2 = gleam@string:drop_end(_pipe@1, 1),
gleam@string:trim(_pipe@2)
end,
case Content of
<<""/utf8>> ->
[];
_ ->
parse_option_list(Content)
end;
false ->
[]
end
end.
-file("src/protozoa/parser.gleam", 1010).
-spec parse_type(binary()) -> proto_type().
parse_type(Type_str) ->
case Type_str of
<<"double"/utf8>> ->
double;
<<"float"/utf8>> ->
float;
<<"int32"/utf8>> ->
int32;
<<"int64"/utf8>> ->
int64;
<<"uint32"/utf8>> ->
u_int32;
<<"uint64"/utf8>> ->
u_int64;
<<"sint32"/utf8>> ->
s_int32;
<<"sint64"/utf8>> ->
s_int64;
<<"fixed32"/utf8>> ->
fixed32;
<<"fixed64"/utf8>> ->
fixed64;
<<"sfixed32"/utf8>> ->
s_fixed32;
<<"sfixed64"/utf8>> ->
s_fixed64;
<<"bool"/utf8>> ->
bool;
<<"string"/utf8>> ->
string;
<<"bytes"/utf8>> ->
bytes;
Other ->
{message_type, Other}
end.
-file("src/protozoa/parser.gleam", 988).
-spec parse_map_type(binary()) -> {ok, {proto_type(), proto_type()}} |
{error, nil}.
parse_map_type(Map_str) ->
case gleam_stdlib:string_starts_with(Map_str, <<"map<"/utf8>>) andalso gleam_stdlib:string_ends_with(
Map_str,
<<">"/utf8>>
) of
true ->
Inner = begin
_pipe = Map_str,
_pipe@1 = gleam@string:drop_start(_pipe, 4),
_pipe@2 = gleam@string:drop_end(_pipe@1, 1),
gleam@string:trim(_pipe@2)
end,
case gleam@string:split(Inner, <<","/utf8>>) of
[Key_str, Value_str] ->
Key_type = parse_type(gleam@string:trim(Key_str)),
Value_type = parse_type(gleam@string:trim(Value_str)),
{ok, {Key_type, Value_type}};
_ ->
{error, nil}
end;
false ->
{error, nil}
end.
-file("src/protozoa/parser.gleam", 925).
-spec parse_map_field(binary(), gleam@option:option(binary())) -> {ok, field()} |
{error, parse_error()}.
parse_map_field(Clean_line, Oneof_name) ->
{Line_without_options, Field_options} = case gleam@string:split_once(
Clean_line,
<<"["/utf8>>
) of
{ok, {Before, After}} ->
case gleam@string:split_once(After, <<"]"/utf8>>) of
{ok, {Options_content, Remaining}} ->
Options = parse_field_options(
<<<<"["/utf8, Options_content/binary>>/binary,
"]"/utf8>>
),
Cleaned_remaining = begin
_pipe = gleam@string:trim(Remaining),
gleam@string:replace(_pipe, <<";"/utf8>>, <<""/utf8>>)
end,
Cleaned_line = case Cleaned_remaining of
<<""/utf8>> ->
gleam@string:trim(Before);
_ ->
<<<<(gleam@string:trim(Before))/binary, " "/utf8>>/binary,
Cleaned_remaining/binary>>
end,
{Cleaned_line, Options};
{error, _} ->
{Clean_line, []}
end;
{error, _} ->
{Clean_line, []}
end,
case gleam@string:split(Line_without_options, <<">"/utf8>>) of
[Map_type_part, Rest] ->
Map_type = <<Map_type_part/binary, ">"/utf8>>,
Parts = gleam@string:split(gleam@string:trim(Rest), <<" "/utf8>>),
case Parts of
[Name, <<"="/utf8>>, Num_str] ->
gleam@result:'try'(
begin
_pipe@1 = parse_map_type(Map_type),
gleam@result:map_error(
_pipe@1,
fun(_) -> {invalid_map_type, Clean_line} end
)
end,
fun(Tuple) ->
{Key_type, Value_type} = Tuple,
case gleam_stdlib:parse_int(Num_str) of
{ok, Num} ->
case Num =< 0 of
true ->
{error,
{invalid_field_number,
Name,
Num_str}};
false ->
{ok,
{field,
Name,
{map, Key_type, Value_type},
Num,
Oneof_name,
Field_options}}
end;
{error, _} ->
{error,
{invalid_field_number, Name, Num_str}}
end
end
);
_ ->
{error, {malformed_field, Clean_line}}
end;
_ ->
{error, {malformed_field, Clean_line}}
end.
-file("src/protozoa/parser.gleam", 1056).
-spec string_to_int(binary()) -> gleam@option:option(integer()).
string_to_int(Str) ->
case gleam_stdlib:parse_int(Str) of
{ok, N} ->
{some, N};
{error, _} ->
none
end.
-file("src/protozoa/parser.gleam", 832).
-spec parse_field(binary(), gleam@option:option(binary())) -> {ok, field()} |
{error, parse_error()}.
parse_field(Clean_line, Oneof_name) ->
Clean_line@1 = case gleam@string:split(Clean_line, <<"//"/utf8>>) of
[Main | _] ->
gleam@string:trim(Main);
[] ->
Clean_line
end,
{Line_without_options, Field_options} = case gleam@string:split_once(
Clean_line@1,
<<"["/utf8>>
) of
{ok, {Before, After}} ->
case gleam@string:split_once(After, <<"]"/utf8>>) of
{ok, {Options_content, Remaining}} ->
Options = parse_field_options(
<<<<"["/utf8, Options_content/binary>>/binary,
"]"/utf8>>
),
Cleaned_remaining = begin
_pipe = gleam@string:trim(Remaining),
gleam@string:replace(_pipe, <<";"/utf8>>, <<""/utf8>>)
end,
Cleaned_line = case Cleaned_remaining of
<<""/utf8>> ->
gleam@string:trim(Before);
_ ->
<<<<(gleam@string:trim(Before))/binary, " "/utf8>>/binary,
Cleaned_remaining/binary>>
end,
{Cleaned_line, Options};
{error, _} ->
{Clean_line@1, []}
end;
{error, _} ->
{Clean_line@1, []}
end,
case gleam@string:split(Line_without_options, <<" "/utf8>>) of
[<<"repeated"/utf8>>, Type_str, Name, <<"="/utf8>>, Num_str] ->
case string_to_int(Num_str) of
{some, Num} ->
case Num =< 0 of
true ->
{error, {invalid_field_number, Name, Num_str}};
false ->
{ok,
{field,
Name,
{repeated, parse_type(Type_str)},
Num,
Oneof_name,
Field_options}}
end;
none ->
{error, {invalid_field_number, Name, Num_str}}
end;
[<<"optional"/utf8>>, Type_str@1, Name@1, <<"="/utf8>>, Num_str@1] ->
case string_to_int(Num_str@1) of
{some, Num@1} ->
case Num@1 =< 0 of
true ->
{error, {invalid_field_number, Name@1, Num_str@1}};
false ->
{ok,
{field,
Name@1,
{optional, parse_type(Type_str@1)},
Num@1,
Oneof_name,
Field_options}}
end;
none ->
{error, {invalid_field_number, Name@1, Num_str@1}}
end;
[Type_str@2, Name@2, <<"="/utf8>>, Num_str@2] ->
case string_to_int(Num_str@2) of
{some, Num@2} ->
case Num@2 =< 0 of
true ->
{error, {invalid_field_number, Name@2, Num_str@2}};
false ->
{ok,
{field,
Name@2,
parse_type(Type_str@2),
Num@2,
Oneof_name,
Field_options}}
end;
none ->
{error, {invalid_field_number, Name@2, Num_str@2}}
end;
_ ->
{error, {malformed_field, Clean_line@1}}
end.
-file("src/protozoa/parser.gleam", 751).
-spec parse_field_line(binary(), gleam@option:option({binary(), list(field())})) -> {ok,
field()} |
{error, parse_error()}.
parse_field_line(Line, Oneof_context) ->
Clean_line = begin
_pipe = Line,
_pipe@1 = gleam@string:replace(_pipe, <<";"/utf8>>, <<""/utf8>>),
gleam@string:trim(_pipe@1)
end,
Clean_line@1 = case gleam@string:split(Clean_line, <<"//"/utf8>>) of
[Main | _] ->
gleam@string:trim(Main);
[] ->
Clean_line
end,
Oneof_name = case Oneof_context of
{some, {Name, _}} ->
{some, Name};
none ->
none
end,
case gleam_stdlib:string_starts_with(Clean_line@1, <<"map<"/utf8>>) of
true ->
parse_map_field(Clean_line@1, Oneof_name);
false ->
parse_field(Clean_line@1, Oneof_name)
end.
-file("src/protozoa/parser.gleam", 1036).
-spec parse_enum_value_line(binary()) -> {ok, enum_value()} | {error, nil}.
parse_enum_value_line(Line) ->
Clean_line = begin
_pipe = Line,
_pipe@1 = gleam@string:replace(_pipe, <<";"/utf8>>, <<""/utf8>>),
_pipe@2 = gleam@string:replace(_pipe@1, <<","/utf8>>, <<""/utf8>>),
gleam@string:trim(_pipe@2)
end,
case gleam@string:split(Clean_line, <<"="/utf8>>) of
[Name_str, Num_str] ->
Name = gleam@string:trim(Name_str),
Num_trimmed = gleam@string:trim(Num_str),
case string_to_int(Num_trimmed) of
{some, Num} ->
{ok, {enum_value, Name, Num}};
none ->
{error, nil}
end;
_ ->
{error, nil}
end.
-file("src/protozoa/parser.gleam", 1031).
-spec parse_enum_values(list(binary())) -> list(enum_value()).
parse_enum_values(Lines) ->
_pipe = Lines,
gleam@list:filter_map(_pipe, fun parse_enum_value_line/1).
-file("src/protozoa/parser.gleam", 463).
-spec parse_enum(binary(), list(binary())) -> {ok,
{gleam@option:option(enum()), list(binary())}} |
{error, parse_error()}.
parse_enum(Line, Rest) ->
case gleam_stdlib:contains_string(Line, <<" {"/utf8>>) orelse gleam@list:any(
Rest,
fun(L) -> gleam@string:trim(L) =:= <<"{"/utf8>> end
) of
false ->
{error, {malformed_enum, Line}};
true ->
Name = begin
_pipe = Line,
_pipe@1 = gleam@string:replace(
_pipe,
<<"enum "/utf8>>,
<<""/utf8>>
),
_pipe@2 = gleam@string:replace(
_pipe@1,
<<" {"/utf8>>,
<<""/utf8>>
),
gleam@string:trim(_pipe@2)
end,
case Name of
<<""/utf8>> ->
{error, {malformed_enum, Line}};
_ ->
{Body, Remaining} = extract_body(Rest, [], 0),
Values = parse_enum_values(Body),
{ok, {{some, {enum, Name, Values}}, Remaining}}
end
end.
-file("src/protozoa/parser.gleam", 1075).
-spec fix_proto_type(proto_type(), list(binary())) -> proto_type().
fix_proto_type(Proto_type, Enum_names) ->
case Proto_type of
{message_type, Name} ->
case gleam@list:contains(Enum_names, Name) of
true ->
{enum_type, Name};
false ->
{message_type, Name}
end;
{repeated, Inner} ->
{repeated, fix_proto_type(Inner, Enum_names)};
{optional, Inner@1} ->
{optional, fix_proto_type(Inner@1, Enum_names)};
{map, Key, Value} ->
{map,
fix_proto_type(Key, Enum_names),
fix_proto_type(Value, Enum_names)};
Other ->
Other
end.
-file("src/protozoa/parser.gleam", 1063).
-spec fix_field_types(list(field()), list(binary())) -> list(field()).
fix_field_types(Fields, Enum_names) ->
gleam@list:map(
Fields,
fun(Field) ->
{field,
erlang:element(2, Field),
fix_proto_type(erlang:element(3, Field), Enum_names),
erlang:element(4, Field),
erlang:element(5, Field),
erlang:element(6, Field)}
end
).
-file("src/protozoa/parser.gleam", 1205).
?DOC(" Parse method signature to extract types and streaming info\n").
-spec parse_method_signature(binary()) -> {ok,
{binary(), binary(), binary(), boolean(), boolean()}} |
{error, nil}.
parse_method_signature(Signature) ->
case gleam@string:split_once(Signature, <<"("/utf8>>) of
{ok, {Method_name, Rest}} ->
Name = gleam@string:trim(Method_name),
case gleam@string:split_once(Rest, <<")"/utf8>>) of
{ok, {Input_part, After_input}} ->
Input_type = gleam@string:trim(Input_part),
Client_streaming = gleam_stdlib:string_starts_with(
Input_type,
<<"stream "/utf8>>
),
Clean_input = case Client_streaming of
true ->
_pipe = gleam@string:drop_start(Input_type, 7),
gleam@string:trim(_pipe);
false ->
Input_type
end,
case gleam@string:split_once(
After_input,
<<"returns"/utf8>>
) of
{ok, {_, Returns_part}} ->
case gleam@string:split_once(
Returns_part,
<<"("/utf8>>
) of
{ok, {_, Output_part}} ->
case gleam@string:split_once(
Output_part,
<<")"/utf8>>
) of
{ok, {Output_type, _}} ->
Output_type@1 = gleam@string:trim(
Output_type
),
Server_streaming = gleam_stdlib:string_starts_with(
Output_type@1,
<<"stream "/utf8>>
),
Clean_output = case Server_streaming of
true ->
_pipe@1 = gleam@string:drop_start(
Output_type@1,
7
),
gleam@string:trim(_pipe@1);
false ->
Output_type@1
end,
{ok,
{Name,
Clean_input,
Clean_output,
Client_streaming,
Server_streaming}};
{error, _} ->
{error, nil}
end;
{error, _} ->
{error, nil}
end;
{error, _} ->
{error, nil}
end;
{error, _} ->
{error, nil}
end;
{error, _} ->
{error, nil}
end.
-file("src/protozoa/parser.gleam", 1179).
?DOC(" Parse a single RPC method definition\n").
-spec parse_single_method(binary()) -> {ok, method()} | {error, parse_error()}.
parse_single_method(Line) ->
Trimmed = begin
_pipe = gleam@string:trim(Line),
gleam@string:replace(_pipe, <<";"/utf8>>, <<""/utf8>>)
end,
case gleam@string:split(Trimmed, <<" "/utf8>>) of
[<<"rpc"/utf8>> | Rest_parts] ->
Combined = gleam@string:join(Rest_parts, <<" "/utf8>>),
case parse_method_signature(Combined) of
{ok,
{Name,
Input_type,
Output_type,
Client_streaming,
Server_streaming}} ->
{ok,
{method,
Name,
Input_type,
Output_type,
Client_streaming,
Server_streaming}};
{error, _} ->
{error, {malformed_field, Line}}
end;
_ ->
{error, {malformed_field, Line}}
end.
-file("src/protozoa/parser.gleam", 1166).
?DOC(" Parse methods within a service body\n").
-spec parse_service_methods(list(binary())) -> {ok, list(method())} |
{error, parse_error()}.
parse_service_methods(Body_lines) ->
Method_lines = gleam@list:filter(
Body_lines,
fun(Line) ->
Trimmed = gleam@string:trim(Line),
gleam_stdlib:string_starts_with(Trimmed, <<"rpc "/utf8>>)
end
),
gleam@list:try_map(Method_lines, fun parse_single_method/1).
-file("src/protozoa/parser.gleam", 1109).
?DOC(" Parse individual service blocks from proto lines\n").
-spec parse_service_blocks(list(binary()), list(service())) -> {ok,
list(service())} |
{error, parse_error()}.
parse_service_blocks(Lines, Services) ->
case Lines of
[] ->
{ok, lists:reverse(Services)};
[Line | Rest] ->
Trimmed = gleam@string:trim(Line),
case gleam_stdlib:string_starts_with(Trimmed, <<"service "/utf8>>) of
true ->
case gleam@string:split(Trimmed, <<" "/utf8>>) of
[<<"service"/utf8>>, Name, <<"{"/utf8>>] ->
Service_name = begin
_pipe = gleam@string:trim(Name),
gleam@string:replace(
_pipe,
<<"{"/utf8>>,
<<""/utf8>>
)
end,
{Body, Remaining} = extract_body(Rest, [], 0),
case parse_service_methods(Body) of
{ok, Methods} ->
Service = {service, Service_name, Methods},
parse_service_blocks(
Remaining,
[Service | Services]
);
{error, Err} ->
{error, Err}
end;
[<<"service"/utf8>>, Name@1] ->
Service_name@1 = gleam@string:trim(Name@1),
case Rest of
[Brace_line | Rest2] ->
case gleam@string:trim(Brace_line) of
<<"{"/utf8>> ->
{Body@1, Remaining@1} = extract_body(
Rest2,
[],
0
),
case parse_service_methods(Body@1) of
{ok, Methods@1} ->
Service@1 = {service,
Service_name@1,
Methods@1},
parse_service_blocks(
Remaining@1,
[Service@1 | Services]
);
{error, Err@1} ->
{error, Err@1}
end;
_ ->
{error, {malformed_message, Line}}
end;
[] ->
{error, {malformed_message, Line}}
end;
_ ->
{error, {malformed_message, Line}}
end;
false ->
parse_service_blocks(Rest, Services)
end
end.
-file("src/protozoa/parser.gleam", 1092).
?DOC(" Parse all services from proto file lines\n").
-spec parse_services(list(binary())) -> {ok, list(service())} |
{error, parse_error()}.
parse_services(Lines) ->
Service_lines = gleam@list:filter(
Lines,
fun(Line) ->
Trimmed = gleam@string:trim(Line),
gleam_stdlib:string_starts_with(Trimmed, <<"service "/utf8>>)
end
),
case Service_lines of
[] ->
{ok, []};
_ ->
parse_service_blocks(Lines, [])
end.
-file("src/protozoa/parser.gleam", 538).
-spec parse_message_body_helper(
list(binary()),
list(oneof()),
list(field()),
gleam@option:option({binary(), list(field())}),
list(message()),
list(enum()),
binary()
) -> {ok, {list(oneof()), list(field()), list(message()), list(enum())}} |
{error, parse_error()}.
parse_message_body_helper(
Lines,
Oneofs,
Fields,
Current_oneof,
Messages,
Enums,
Message_name
) ->
case Lines of
[] ->
case Current_oneof of
{some, {Name, Oneof_fields}} ->
{ok,
{lists:reverse(
[{oneof, Name, lists:reverse(Oneof_fields)} |
Oneofs]
),
lists:reverse(Fields),
lists:reverse(Messages),
lists:reverse(Enums)}};
none ->
{ok,
{lists:reverse(Oneofs),
lists:reverse(Fields),
lists:reverse(Messages),
lists:reverse(Enums)}}
end;
[Line | Rest] ->
Trimmed = gleam@string:trim(Line),
case Trimmed of
<<""/utf8>> ->
parse_message_body_helper(
Rest,
Oneofs,
Fields,
Current_oneof,
Messages,
Enums,
Message_name
);
_ ->
case gleam_stdlib:string_starts_with(
Trimmed,
<<"oneof "/utf8>>
) of
true ->
case Current_oneof of
{some, {Name@1, Oneof_fields@1}} ->
New_oneof = {oneof,
Name@1,
lists:reverse(Oneof_fields@1)},
Oneof_name = begin
_pipe = gleam@string:replace(
Trimmed,
<<"oneof "/utf8>>,
<<""/utf8>>
),
_pipe@1 = gleam@string:replace(
_pipe,
<<" {"/utf8>>,
<<""/utf8>>
),
gleam@string:trim(_pipe@1)
end,
parse_message_body_helper(
Rest,
[New_oneof | Oneofs],
Fields,
{some, {Oneof_name, []}},
Messages,
Enums,
Message_name
);
none ->
Oneof_name@1 = begin
_pipe@2 = gleam@string:replace(
Trimmed,
<<"oneof "/utf8>>,
<<""/utf8>>
),
_pipe@3 = gleam@string:replace(
_pipe@2,
<<" {"/utf8>>,
<<""/utf8>>
),
gleam@string:trim(_pipe@3)
end,
parse_message_body_helper(
Rest,
Oneofs,
Fields,
{some, {Oneof_name@1, []}},
Messages,
Enums,
Message_name
)
end;
false ->
case gleam_stdlib:string_starts_with(
Trimmed,
<<"message "/utf8>>
) of
true ->
gleam@result:'try'(
parse_message(Line, Rest),
fun(_use0) ->
{Msg, Remaining} = _use0,
case Msg of
{some, M} ->
parse_message_body_helper(
Remaining,
Oneofs,
Fields,
Current_oneof,
[M | Messages],
Enums,
Message_name
);
none ->
parse_message_body_helper(
Remaining,
Oneofs,
Fields,
Current_oneof,
Messages,
Enums,
Message_name
)
end
end
);
false ->
case gleam_stdlib:string_starts_with(
Trimmed,
<<"enum "/utf8>>
) of
true ->
gleam@result:'try'(
parse_enum(Line, Rest),
fun(_use0@1) ->
{Enum, Remaining@1} = _use0@1,
case Enum of
{some, E} ->
parse_message_body_helper(
Remaining@1,
Oneofs,
Fields,
Current_oneof,
Messages,
[E | Enums],
Message_name
);
none ->
parse_message_body_helper(
Remaining@1,
Oneofs,
Fields,
Current_oneof,
Messages,
Enums,
Message_name
)
end
end
);
false ->
case Trimmed =:= <<"}"/utf8>> of
true ->
case Current_oneof of
{some,
{Name@2,
Oneof_fields@2}} ->
New_oneof@1 = {oneof,
Name@2,
lists:reverse(
Oneof_fields@2
)},
parse_message_body_helper(
Rest,
[New_oneof@1 |
Oneofs],
Fields,
none,
Messages,
Enums,
Message_name
);
none ->
parse_message_body_helper(
Rest,
Oneofs,
Fields,
Current_oneof,
Messages,
Enums,
Message_name
)
end;
false ->
case parse_field_line(
Trimmed,
Current_oneof
) of
{ok, Field} ->
case Current_oneof of
{some,
{Name@3,
Oneof_fields@3}} ->
parse_message_body_helper(
Rest,
Oneofs,
Fields,
{some,
{Name@3,
[Field |
Oneof_fields@3]}},
Messages,
Enums,
Message_name
);
none ->
parse_message_body_helper(
Rest,
Oneofs,
[Field |
Fields],
Current_oneof,
Messages,
Enums,
Message_name
)
end;
{error, _} ->
{error,
{malformed_field,
Trimmed}}
end
end
end
end
end
end
end.
-file("src/protozoa/parser.gleam", 429).
-spec parse_message(binary(), list(binary())) -> {ok,
{gleam@option:option(message()), list(binary())}} |
{error, parse_error()}.
parse_message(Line, Rest) ->
case gleam_stdlib:contains_string(Line, <<" {"/utf8>>) orelse gleam@list:any(
Rest,
fun(L) -> gleam@string:trim(L) =:= <<"{"/utf8>> end
) of
false ->
{error, {malformed_message, Line}};
true ->
Name = begin
_pipe = Line,
_pipe@1 = gleam@string:replace(
_pipe,
<<"message "/utf8>>,
<<""/utf8>>
),
_pipe@2 = gleam@string:replace(
_pipe@1,
<<" {"/utf8>>,
<<""/utf8>>
),
gleam@string:trim(_pipe@2)
end,
case Name of
<<""/utf8>> ->
{error, {malformed_message, Line}};
_ ->
{Body, Remaining} = extract_body(Rest, [], 0),
gleam@result:'try'(
parse_message_body(Body, Name),
fun(_use0) ->
{Oneofs, Regular_fields, Nested_messages, Enums} = _use0,
{ok,
{{some,
{message,
Name,
Regular_fields,
Oneofs,
Nested_messages,
Enums}},
Remaining}}
end
)
end
end.
-file("src/protozoa/parser.gleam", 531).
-spec parse_message_body(list(binary()), binary()) -> {ok,
{list(oneof()), list(field()), list(message()), list(enum())}} |
{error, parse_error()}.
parse_message_body(Lines, Message_name) ->
parse_message_body_helper(Lines, [], [], none, [], [], Message_name).
-file("src/protozoa/parser.gleam", 328).
-spec parse_items_helper(list(binary()), list(message()), list(enum())) -> {ok,
{list(message()), list(enum())}} |
{error, parse_error()}.
parse_items_helper(Lines, Messages, Enums) ->
case Lines of
[] ->
{ok, {lists:reverse(Messages), lists:reverse(Enums)}};
[Line | Rest] ->
case gleam_stdlib:string_starts_with(Line, <<"message "/utf8>>) of
true ->
gleam@result:'try'(
parse_message(Line, Rest),
fun(_use0) ->
{Msg, Remaining} = _use0,
case Msg of
{some, M} ->
parse_items_helper(
Remaining,
[M | Messages],
Enums
);
none ->
parse_items_helper(
Remaining,
Messages,
Enums
)
end
end
);
false ->
case gleam_stdlib:string_starts_with(Line, <<"enum "/utf8>>) of
true ->
gleam@result:'try'(
parse_enum(Line, Rest),
fun(_use0@1) ->
{Enum, Remaining@1} = _use0@1,
case Enum of
{some, E} ->
parse_items_helper(
Remaining@1,
Messages,
[E | Enums]
);
none ->
parse_items_helper(
Remaining@1,
Messages,
Enums
)
end
end
);
false ->
case gleam_stdlib:string_starts_with(
Line,
<<"service "/utf8>>
) of
true ->
{_, Remaining@2} = extract_body(Rest, [], 0),
parse_items_helper(
Remaining@2,
Messages,
Enums
);
false ->
Trimmed = gleam@string:trim(Line),
case Trimmed of
<<""/utf8>> ->
parse_items_helper(
Rest,
Messages,
Enums
);
_ ->
case gleam_stdlib:contains_string(
Trimmed,
<<"{"/utf8>>
)
orelse gleam_stdlib:contains_string(
Trimmed,
<<"}"/utf8>>
) of
true ->
{error,
{invalid_syntax,
Line,
<<"unrecognized syntax"/utf8>>}};
false ->
parse_items_helper(
Rest,
Messages,
Enums
)
end
end
end
end
end
end.
-file("src/protozoa/parser.gleam", 322).
-spec parse_items(list(binary())) -> {ok, {list(message()), list(enum())}} |
{error, parse_error()}.
parse_items(Lines) ->
parse_items_helper(Lines, [], []).
-file("src/protozoa/parser.gleam", 209).
?DOC(
" Parses a simple Protocol Buffer file from its text content.\n"
" This is a simplified parser that handles basic proto3 syntax.\n"
" \n"
" ## Limitations\n"
" - Only supports proto3 syntax\n"
" - Limited support for nested types\n"
" \n"
" ## Examples\n"
" \n"
" ```gleam\n"
" let proto_content = \"syntax = 'proto3'; message Person { string name = 1; }\"\n"
" let proto_file = parse(proto_content)\n"
" ```\n"
).
-spec parse(binary()) -> {ok, proto_file()} | {error, parse_error()}.
parse(Content) ->
Lines = begin
_pipe = Content,
_pipe@1 = gleam@string:split(_pipe, <<"\n"/utf8>>),
_pipe@2 = gleam@list:map(_pipe@1, fun gleam@string:trim/1),
gleam@list:filter(
_pipe@2,
fun(Line) ->
(Line /= <<""/utf8>>) andalso not gleam_stdlib:string_starts_with(
Line,
<<"//"/utf8>>
)
end
)
end,
case Lines of
[] ->
{error, {missing_required_syntax, <<"proto content"/utf8>>}};
_ ->
gleam@result:'try'(
find_syntax(Lines),
fun(Syntax) ->
Package = find_package(Lines),
gleam@result:'try'(
find_imports(Lines),
fun(Imports) ->
gleam@result:'try'(
parse_items(Lines),
fun(_use0) ->
{Messages, Enums} = _use0,
gleam@result:'try'(
parse_services(Lines),
fun(Services) ->
gleam@result:'try'(
validate_messages(Messages),
fun(Validated_messages) ->
Enum_names = gleam@list:map(
Enums,
fun(E) ->
erlang:element(2, E)
end
),
Messages@1 = gleam@list:map(
Validated_messages,
fun(Msg) ->
{message,
erlang:element(
2,
Msg
),
fix_field_types(
erlang:element(
3,
Msg
),
Enum_names
),
erlang:element(
4,
Msg
),
erlang:element(
5,
Msg
),
erlang:element(
6,
Msg
)}
end
),
{ok,
{proto_file,
Syntax,
Package,
Imports,
Messages@1,
Enums,
Services}}
end
)
end
)
end
)
end
)
end
)
end.