-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 = <"/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.