Packages
gpb
4.21.3
5.0.0
4.21.7
4.21.6
4.21.5
4.21.4
4.21.3
4.21.2
4.21.1
4.21.0
4.20.0
4.19.9
4.19.8
4.19.7
4.19.6
4.19.5
4.19.4
4.19.3
4.19.2
4.19.1
4.19.0
4.18.0
4.17.7
4.17.6
4.17.5
4.17.3
4.17.2
4.17.1
4.17.0
4.16.2
4.16.1
4.16.0
4.15.2
4.15.1
4.14.2
4.14.1
4.14.0
4.13.0
4.12.0
4.11.2
4.11.1
4.11.0
4.10.6
4.10.5
4.10.4
4.10.3
4.10.2
4.10.1
4.10.0
4.9.3
4.9.2
4.9.1
4.9.0
4.8.0
4.7.3
4.7.2
4.7.1
4.7.0
4.6.0
4.5.1
4.5.0
4.4.1
4.4.0
4.3.3
4.3.2
4.3.1
4.3.0
4.2.3
4.2.2
4.2.1
4.2.0
4.1.9
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.2
4.0.1
4.0.0
3.28.1
3.28.0
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.8
3.26.7
3.26.6
3.26.5
3.26.4
3.26.3
3.26.2
3.26.1
3.26.0
3.25.2
3.25.1
3.25.0
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.2
3.23.1
3.23.0
3.22.5
3.22.4
3.22.3
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.3
3.20.2
3.20.0
3.19.0
3.18.10
3.18.9
3.18.8
3.18.7
3.18.6
3.18.5
3.18.4
3.18.3
3.18.2
3.18.1
3.18.0
3.17.13
3.17.12
3.17.11
3.17.10
3.17.9
3.17.8
3.17.5
3.17.4
3.17.3
3.17.2
3.17.1
3.17.0
3.16.0
3.15.0
3.14.0
3.13.0
3.12.2
3.12.1
3.12.0
3.11.0
A compiler for Google protocol buffer definitions files for Erlang.
Current section
Files
Jump to
Current section
Files
src/gpb_scan.erl
%%% Copyright (C) 2019 Tomas Abrahamsson
%%%
%%% Author: Tomas Abrahamsson <tab@lysator.liu.se>
%%%
%%% This library is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU Lesser General Public
%%% License as published by the Free Software Foundation; either
%%% version 2.1 of the License, or (at your option) any later version.
%%%
%%% This library is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% Lesser General Public License for more details.
%%%
%%% You should have received a copy of the GNU Lesser General Public
%%% License along with this library; if not, write to the Free Software
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
%%% MA 02110-1301 USA
%% @doc This is the scanner
%% @private
-module(gpb_scan).
-export([binary/1, binary/2]).
-export([format_error/1]).
-export_type([token/0]).
-export_type([pos/0]).
-export_type([error/0]).
%% -- bwd compat -- do not use these ----------------------------------
%% Deprecated!
%% Instead, to retrieve proto definitions,
%% use the option to_proto_defs
%% with gpb_compile:file or gpb_compile:string.
%% For exprotobuf, see also
%% https://github.com/bitwalker/exprotobuf/issues/114
-export([string/1]). % use opt to_proto_defs instead
%% --^^--- bwd compat -- do not use these ----------------------------
-export([read_number/1]).
-type token() :: {token_data(), pos(), orig_text()}.
-type token_data() :: atom() | % punctuation(-like) characters, generally
binary() | % words
{str_lit, string()} | % quoted strings
{float_lit, float()} |
{int_lit, {int_format(), integer()}}.
-type int_format() :: dec | hex | oct.
-type orig_text() :: binary() | atom().
-type pos() :: LineNumber::pos_integer().
-type error() :: {LineNumber::pos_integer(), module(), Reason::term()}.
%% @doc Tokenize input. Comments and whitespace are skipped over.
%% Input is expected to be UTF-8, or at least the strings in the input are.
%% No byte order mark is expected in the input binary.
%%
%% The format of the return values (both on success and error) mimic
%% those of a leex-generated scanner.
%%
%% Please, do not use this from outside of gpb. Instead, use the option
%% `to_proto_defs' with gpb_compile:file/1,2 or gob_string/2,3, to get
%% a parsed .proto file.
%%
%% @hidden
%% @equiv binary(Bin, 1)
-spec binary(binary()) ->
{ok, [token()], EndLine::pos_integer()} |
{error, error(), LineNumber::pos_integer()}.
binary(Bin) ->
binary(Bin, 1).
-spec binary(binary(), StartLine::pos_integer()) ->
{ok, [token()], EndLine::pos_integer()} |
{error, error(), LineNumber::pos_integer()}.
binary(Bin, Line) ->
b(Bin, Line, []).
-define(f(Fmt, Args), io_lib:format(Fmt, Args)).
format_error({syntax_error, {error, {before, TrailingText}}}) ->
?f("syntax error before ~s", [TrailingText]);
format_error({syntax_error, unterminated_comment}) ->
"unterminated comment";
format_error({syntax_error, {non_utf8_string, StrAcc}}) ->
?f("non UTF-8 string ~p", [StrAcc]);
format_error({syntax_error, {unterminated_string, StrAcc}}) ->
?f("unterminated string starting with ~s", [StrAcc]);
format_error({syntax_error, {invalid_code_point, Reason, C}}) ->
?f("invalid code point ~p char (~w)", [Reason, C]);
format_error({syntax_error, {unterminated_numeral, Bin}}) ->
?f("unterminated numeral ~s", [Bin]);
format_error({syntax_error, {invalid_number, {before, TrailingText}}}) ->
?f("invalid number before ~s", [TrailingText]);
format_error({syntax_error, {invalid_number, {need_space, X}}}) ->
?f("need space after number, found ~c", [X]);
format_error({syntax_error, {invalid_octal, Bin, {decimal_digit, D}}}) ->
?f("number with leading zero is octal, invalid digit ~c in ~s", [D, Bin]);
format_error({syntax_error, {invalid_number, Bin, {need_space, X}}}) ->
?f("need space after number, found ~c in ~s", [X, Bin]);
format_error({syntax_error, {invalid_number, multiple_decimal_points}}) ->
?f("invalid number, multiple decimal points", []);
format_error({syntax_error, {invalid_number, decimal_point_in_exp}}) ->
?f("invalid number, decimal point in exponent", []).
-define(is_white(C), (0 =< C andalso C =< $\s)).
-define(is_lower(C), ($a =< C andalso C =< $z)).
-define(is_upper(C), ($A =< C andalso C =< $Z)).
-define(is_letter(C), (?is_lower(C) orelse ?is_upper(C) orelse (C =:= $_))).
-define(is_digit(C), ($0 =< C andalso C =< $9)).
-define(is_a_to_f(C), (($a =< C andalso C =< $f)
orelse ($A =< C andalso C =< $F))).
-define(is_octal(C), ($0 =< C andalso C =< $7)).
-define(is_sign(C), (C =:= $+ orelse C =:= $-)).
-define(is_exp(C), (C =:= $e orelse C =:= $E)).
b(Bin, Line, Acc) ->
case Bin of
<<"//", Rest/binary>> -> skip_cxx_comment(Rest, Line, Acc);
<<"/*", Rest/binary>> -> skip_c_comment(Rest, Line, Line, Acc);
<<"\n", Rest/binary>> -> b(Rest, Line + 1, Acc);
<<W, Rest/binary>> when ?is_white(W) -> b(Rest, Line, Acc);
<<"\"", Rest/binary>> ->
read_string(Rest, $\", <<>>, Line, Line, <<"\"">>, Acc);
<<"'", Rest/binary>> ->
read_string(Rest, $', <<>>, Line, Line, <<"'">>, Acc);
<<D, _/binary>> when ?is_digit(D) -> read_number(Bin, Line, Acc);
<<$., D, _/binary>> when ?is_digit(D) -> read_number(Bin, Line, Acc);
<<L, Rest/binary>> when ?is_letter(L) ->
read_word(Rest, <<L>>, Line, Acc);
<<$., Rest/binary>> -> b(Rest, Line, [{'.', Line, '.'} | Acc]);
<<$:, Rest/binary>> -> b(Rest, Line, [{':', Line, ':'} | Acc]);
<<$;, Rest/binary>> -> b(Rest, Line, [{';', Line, ';'} | Acc]);
<<${, Rest/binary>> -> b(Rest, Line, [{'{', Line, '{'} | Acc]);
<<$}, Rest/binary>> -> b(Rest, Line, [{'}', Line, '}'} | Acc]);
<<$[, Rest/binary>> -> b(Rest, Line, [{'[', Line, '['} | Acc]);
<<$], Rest/binary>> -> b(Rest, Line, [{']', Line, ']'} | Acc]);
<<$(, Rest/binary>> -> b(Rest, Line, [{'(', Line, '('} | Acc]);
<<$), Rest/binary>> -> b(Rest, Line, [{')', Line, ')'} | Acc]);
<<$=, Rest/binary>> -> b(Rest, Line, [{'=', Line, '='} | Acc]);
<<$,, Rest/binary>> -> b(Rest, Line, [{',', Line, ','} | Acc]);
<<$<, Rest/binary>> -> b(Rest, Line, [{'<', Line, '<'} | Acc]);
<<$>, Rest/binary>> -> b(Rest, Line, [{'>', Line, '>'} | Acc]);
<<$-, Rest/binary>> -> b(Rest, Line, [{'-', Line, '-'} | Acc]);
<<$+, Rest/binary>> -> b(Rest, Line, [{'+', Line, '+'} | Acc]);
<<_, _/binary>> -> syntax_error({error, before(Bin)}, Line);
<<>> -> ok(Line, Acc)
end.
%% -- comments --
skip_cxx_comment(Bin, Line, Acc) ->
case Bin of
<<"\n", Rest/binary>> -> b(Rest, Line + 1, Acc);
<<_, Rest/binary>> -> skip_cxx_comment(Rest, Line, Acc);
<<>> -> ok(Line, Acc)
end.
skip_c_comment(Bin, Line0, Line, Acc) ->
%% Block comments cannot be nested, should we warn if we see /* ??
case Bin of
<<"*/", Rest/binary>> -> b(Rest, Line, Acc);
<<"\n", Rest/binary>> -> skip_c_comment(Rest, Line0, Line + 1, Acc);
<<_, Rest/binary>> -> skip_c_comment(Rest, Line0, Line, Acc);
<<>> -> syntax_error(unterminated_comment, Line0)
end.
%% -- string --
read_string(Bin, Quote, StrAcc, Line0, Line, Orig, Acc) ->
case Bin of
<<Quote, Rest/binary>> ->
case unicode:characters_to_list(StrAcc, utf8) of
S when is_list(S) ->
Orig1 = <<Orig/binary, Quote>>,
b(Rest, Line, [{{str_lit, S}, Line, Orig1} | Acc]);
{error, _SuccessfullyConverted, _Offending} ->
syntax_error({non_utf8_string, StrAcc}, Line0);
{incomplete, _SuccessfullyConverted, _IncompleteChar} ->
syntax_error({non_utf8_string, StrAcc}, Line0)
end;
<<"\\u", Rest/binary>> -> % 4-digit (up to) char in hex
case collect(Rest, fun is_hex_char/1, 4) of
{B2, Rest2} ->
C = hex_to_integer(B2),
Orig1 = <<Orig/binary, "\\u", B2/binary>>,
add_code_point(Rest2, Quote, StrAcc, C, Line0, Line,
Orig1, Acc);
eof ->
syntax_error({unterminated_string, StrAcc}, Line0)
end;
<<"\\U", Rest/binary>> -> % 8-digit (up to) char in hex
case collect(Rest, fun is_hex_char/1, 8) of
{B2, Rest2} ->
C = hex_to_integer(B2),
Orig1 = <<Orig/binary, "\\U", B2/binary>>,
add_code_point(Rest2, Quote, StrAcc, C, Line0, Line,
Orig1, Acc);
eof ->
syntax_error({unterminated_string, StrAcc}, Line0)
end;
<<"\\x", Rest/binary>> -> % 2-digit (up to) char in hex
case collect(Rest, fun is_hex_char/1, 2) of
{B2, Rest2} ->
C = hex_to_integer(B2),
Orig1 = <<Orig/binary, "\\x", B2/binary>>,
add_code_point(Rest2, Quote, StrAcc, C, Line0, Line,
Orig1, Acc);
eof ->
syntax_error({unterminated_string, StrAcc}, Line0)
end;
<<"\\", O, _/binary>> when ?is_octal(O) -> % 3-digits (up to) in oct
<<"\\", Rest/binary>> = Bin,
case collect(Rest, fun is_oct_char/1, 3) of
{B2, Rest2} ->
C = oct_to_integer(B2),
Orig1 = <<Orig/binary, "\\", B2/binary>>,
add_code_point(Rest2, Quote, StrAcc, C, Line0, Line,
Orig1, Acc);
eof ->
syntax_error({unterminated_string, StrAcc}, Line0)
end;
<<"\\", C, Rest/binary>> ->
C2 = escape_char(C),
Orig1 = <<Orig/binary, "\\", C>>,
read_string(Rest, Quote, <<StrAcc/binary, C2>>, Line0, Line,
Orig1, Acc);
<<C, Rest/binary>> ->
Orig1 = <<Orig/binary, C>>,
read_string(Rest, Quote, <<StrAcc/binary, C>>, Line0, Line,
Orig1, Acc);
<<>> ->
syntax_error({unterminated_string, StrAcc}, Line0)
end.
add_code_point(Bin, Quote, StrAcc, C, Line0, Line, Orig, Acc) ->
try <<StrAcc/binary, C/utf8>> of
StrAcc2 ->
read_string(Bin, Quote, StrAcc2, Line0, Line, Orig, Acc)
catch error:badarg ->
Reason = explain_invalid_code_point(C),
syntax_error({invalid_code_point, Reason, C}, Line)
end.
explain_invalid_code_point(C) ->
if C > 16#10ffff -> too_large;
16#d800 =< C, C =< 16#dbff -> high_half_of_utf16_surrogate_pair;
16#dc00 =< C, C =< 16#dfff -> low_half_of_utf16_surrogate_pair;
true -> other % can't happen?
end.
escape_char($a) -> 7; %\a = BEL
escape_char($b) -> $\b; %\b = BS
escape_char($f) -> $\f; %\f = FF
escape_char($n) -> $\n; %\n = LF
escape_char($r) -> $\r; %\r = CR
escape_char($t) -> $\t; %\t = TAB
escape_char($v) -> $\v; %\v = VT
escape_char(C) -> C.
%% -- number --
read_number(Str) when is_list(Str) ->
read_number(unicode:characters_to_binary(Str));
read_number(<<"inf">>) -> {ok, infinity};
read_number(<<"-inf">>) -> {ok, '-infinity'};
read_number(<<"nan">>) -> {ok, 'nan'};
read_number(Bin) when is_binary(Bin) ->
{Sign, Bin2} = case Bin of
<<"+", Rest/binary>> -> {1, Rest};
<<"-", Rest/binary>> -> {-1, Rest};
_ -> {1, Bin}
end,
case read_number(Bin2, 1, []) of
{ok, [{{float_lit, V}, _Pos, _Orig}], _End} -> {ok, Sign * V};
{ok, [{{int_lit, {dec, V}}, _Pos, _Orig}], _End} -> {ok, Sign * V};
{ok, [{{int_lit, {oct, V}}, _Pos, _Orig}], _End} -> {ok, Sign * V};
{ok, [{{int_lit, {hex, V}}, _Pos, _Orig}], _End} -> {ok, Sign * V};
{error, Reason, _Line} -> {error, Reason}
end.
read_number(Bin, Line, Acc) -> % always positive, sign is another token
case Bin of
<<"0.", Rest/binary>> -> read_frac(Rest, <<"0.">>, Line, <<"0.">>, Acc);
<<"0e", Rest/binary>> -> read_exp(Rest, <<"0.0e">>,Line, <<"0e">>, Acc);
<<"0x", Rest/binary>> -> read_hexnum(Rest, Line, <<"0x">>, Acc);
<<"0X", Rest/binary>> -> read_hexnum(Rest, Line, <<"0X">>, Acc);
<<"0", _/binary>> -> read_octnum(Bin, Line, <<>>, Acc);
_ -> read_num(Bin, <<>>, Line, <<>>, Acc)
end.
read_hexnum(Bin, Line, Orig, Acc) ->
case collect(Bin, fun is_hex_char/1, infinity) of
{_, <<X, _/binary>>} when ?is_letter(X) ->
syntax_error({invalid_number, Bin, {need_space, X}}, Line);
{B2, Rest} ->
N = hex_to_integer(B2),
Orig1 = <<Orig/binary, B2/binary>>,
b(Rest, Line, [{{int_lit, {hex, N}}, Line, Orig1} | Acc]);
eof ->
syntax_error({unterminated_numeral, Bin}, Line)
end.
read_octnum(Bin, Line, Orig, Acc) ->
case collect(Bin, fun is_oct_char/1, infinity) of
{_, <<$8, _/binary>>} ->
syntax_error({invalid_octal, Bin, {decimal_digit, 8}}, Line);
{_, <<$9, _/binary>>} ->
syntax_error({invalid_octal, Bin, {decimal_digit, 9}}, Line);
{_, <<X, _/binary>>} when ?is_letter(X) ->
syntax_error({invalid_number, Bin, {need_space, X}}, Line);
{B2, Rest} ->
Orig1 = <<Orig/binary, B2/binary>>,
case oct_to_integer(B2) of
0 ->
b(Rest, Line, [{{int_lit, {dec, 0}}, Line, Orig1} | Acc]);
N ->
b(Rest, Line, [{{int_lit, {oct, N}}, Line, Orig1} | Acc])
end;
eof ->
syntax_error({unterminated_numeral, Bin}, Line)
end.
%% Num -> Digits . Digits Exp?
%% | . Digits Exp?
%% | Digits . Exp?
%% | Digits Exp?
%% Exp -> (E|e)(+|-)?Digits
read_num(Bin, StrAcc, Line, Orig, Acc) ->
case Bin of
<<$., Rest/binary>> ->
Orig1 = <<Orig/binary, $.>>,
StrAcc2 = <<StrAcc/binary, $.>>,
StrAcc3 = ensure_0_dot(StrAcc2),
read_frac(Rest, StrAcc3, Line, Orig1, Acc);
<<E, Rest/binary>> when ?is_exp(E) ->
if StrAcc =:= <<>> ->
syntax_error({invalid_number, before(Bin)}, Line);
StrAcc =/= <<>> ->
Orig1 = <<Orig/binary, $E>>,
StrAcc2 = ensure_digits_dot_digits(StrAcc),
StrAcc3 = <<StrAcc2/binary, "e">>,
read_exp(Rest, StrAcc3, Line, Orig1, Acc)
end;
<<D, Rest/binary>> when ?is_digit(D) ->
Orig1 = <<Orig/binary, D>>,
read_num(Rest, <<StrAcc/binary, D>>, Line, Orig1, Acc);
<<X, _/binary>> when ?is_letter(X) ->
syntax_error({invalid_number, {need_space, X}}, Line);
_ ->
if StrAcc =:= <<>> ->
syntax_error({invalid_number, before(Bin)}, Line);
StrAcc =/= <<>> ->
N = list_to_integer(binary_to_list(StrAcc)),
b(Bin, Line, [{{int_lit, {dec, N}}, Line, Orig} | Acc])
end
end.
read_frac(Bin, StrAcc, Line, Orig, Acc) ->
case Bin of
<<D, Rest/binary>> when ?is_digit(D) ->
Orig1 = <<Orig/binary, D>>,
read_frac(Rest, <<StrAcc/binary, D>>, Line, Orig1, Acc);
<<E, Rest/binary>> when ?is_exp(E) ->
Orig1 = <<Orig/binary, E>>,
StrAcc2 = ensure_digits_dot_digits(StrAcc),
StrAcc3 = <<StrAcc2/binary, "e">>,
read_exp(Rest, StrAcc3, Line, Orig1, Acc);
<<X, _/binary>> when ?is_letter(X) ->
syntax_error({invalid_number, {need_space, X}}, Line);
<<$., _/binary>> ->
syntax_error({invalid_number, multiple_decimal_points}, Line);
_ ->
StrAcc2 = ensure_digits_dot_digits(StrAcc),
try list_to_float(binary_to_list(StrAcc2)) of
Float ->
b(Bin, Line, [{{float_lit, Float}, Line, Orig} | Acc])
catch error:badarg ->
syntax_error({invalid_number, before(Bin)}, Line)
end
end.
read_exp(Bin, StrAcc, Line, Orig, Acc) ->
case Bin of
<<S, Rest/binary>> when ?is_sign(S)->
Orig1 = <<Orig/binary, S>>,
read_exp_d(Rest, <<StrAcc/binary, S>>, Line, Orig1, Acc);
<<D, Rest/binary>> when ?is_digit(D) ->
Orig1 = <<Orig/binary, D>>,
read_exp_d(Rest, <<StrAcc/binary, D>>, Line, Orig1, Acc);
<<X, _/binary>> when ?is_letter(X) ->
syntax_error({invalid_number, {need_space, X}}, Line);
<<$., _/binary>> ->
syntax_error({invalid_number, decimal_point_in_exp}, Line);
_ ->
syntax_error({invalid_number, before(Bin)}, Line)
end.
read_exp_d(Bin, StrAcc, Line, Orig, Acc) ->
case Bin of
<<D, Rest/binary>> when ?is_digit(D) ->
Orig1 = <<Orig/binary, D>>,
read_exp_d(Rest, <<StrAcc/binary, D>>, Line, Orig1, Acc);
<<X, _/binary>> when ?is_letter(X) ->
syntax_error({invalid_number, {need_space, X}}, Line);
<<$., _/binary>> ->
syntax_error({invalid_number, decimal_point_in_exp}, Line);
_ ->
try list_to_float(binary_to_list(StrAcc)) of
Float ->
b(Bin, Line, [{{float_lit, Float}, Line, Orig} | Acc])
catch error:badarg ->
syntax_error({invalid_number, before(Bin)}, Line)
end
end.
ensure_0_dot(<<".">>) -> <<"0.">>;
ensure_0_dot(X) -> X.
ensure_digits_dot_digits(B) -> 'd.d'(B, B).
'd.d'(<<D, Rest/binary>>, B) when ?is_digit(D) -> 'd.d'(Rest, B);
'd.d'(<<>>, B) -> <<B/binary, ".0">>;
'd.d'(<<".">>, B) -> <<B/binary, "0">>;
'd.d'(<<".", _/binary>>, B) -> B.
%% -- word --
read_word(Bin, StrAcc, Line, Acc) ->
case Bin of
<<L, Rest/binary>> when ?is_letter(L) ->
read_word(Rest, <<StrAcc/binary, L>>, Line, Acc);
<<D, Rest/binary>> when ?is_digit(D) ->
read_word(Rest, <<StrAcc/binary, D>>, Line, Acc);
_ ->
b(Bin, Line, [{StrAcc, Line, StrAcc} | Acc])
end.
%% -- common helpers --
collect(Bin, Pred, MaxLen) ->
collect2(Bin, Pred, MaxLen, <<>>).
collect2(<<C, Rest/binary>> = Bin, Pred, N, Acc) when N >= 1 ->
case Pred(C) of
true -> collect2(Rest, Pred, decrement(N), <<Acc/binary, C>>);
false -> {Acc, Bin}
end;
collect2(Rest, _Pred, _N, Acc) ->
if Rest =:= <<>>,
Acc =:= <<>> ->
eof;
true ->
{Acc, Rest}
end.
decrement(infinity) -> infinity;
decrement(N) when is_integer(N) -> N - 1.
is_oct_char(C) -> ?is_octal(C).
is_hex_char(C) -> ?is_digit(C) orelse ?is_a_to_f(C).
oct_to_integer(Bin) -> erlang:list_to_integer(binary_to_list(Bin), 8).
hex_to_integer(Bin) -> erlang:list_to_integer(binary_to_list(Bin), 16).
ok(Line, Acc) ->
{ok, lists:reverse(Acc), Line}.
syntax_error(Reason, Line) ->
%% FIXME: error spec?
{error, {Line, ?MODULE, {syntax_error, Reason}}, Line}.
before(Bin) -> {before, get_line(Bin, <<>>)}.
get_line(<<"\n", _/binary>>, Acc) -> Acc;
get_line(<<C, Rest/binary>>, Acc) -> get_line(Rest, <<Acc/binary, C>>);
get_line(<<>>, <<>>) -> 'end-of-input';
get_line(<<>>, Acc) -> Acc.
%% -- bwd compat -- do not use these ----------------------------------
%% Deprecated!
%% Instead, to retrieve proto definitions,
%% use the option to_proto_defs
%% with gpb_compile:file or gpb_compile:string.
%% For exprotobuf, see also
%% https://github.com/bitwalker/exprotobuf/issues/114
%% Use the option to_proto_defs with gpb_compile:file/string instead.
string(S) ->
binary(unicode:characters_to_binary(S)).
%% --^^--- bwd compat -- do not use these ----------------------------