Current section
Files
Jump to
Current section
Files
src/ephp_lib_pcre.erl
-module(ephp_lib_pcre).
-author('manuel@altenwald.com').
-behaviour(ephp_lib).
-export([init_func/0, init_config/0, init_const/0, preg_match/7, preg_replace/7,
preg_quote/4]).
-define(PREG_OFFSET_CAPTURE, 256).
-include("ephp.hrl").
-include("ephp_parser.hrl").
-spec init_func() -> ephp_lib:php_function_results().
init_func() ->
[{preg_match,
[{args,
{2, 5, false, [string, string, {raw, ephp_array:new()}, {integer, 0}, {integer, 0}]}}]},
{preg_replace,
[{args, {3, 5, undefined, [mixed, mixed, mixed, {integer, -1}, {integer, 0}]}}]},
{preg_quote, [{args, {1, 2, undefined, [string, {string, <<>>}]}}]}].
-spec init_config() -> ephp_lib:php_config_results().
init_config() ->
[{<<"pcre.backtrack_limit">>, 1000000}, {<<"pcre.recurssion_limit">>, 100000}].
-ifndef(PCRE_VERSION).
%% keep it only for some editors (like VSCode)
-define(PCRE_VERSION, "8.02 2010-03-19").
-endif.
-spec init_const() -> ephp_lib:php_const_results().
init_const() ->
[{<<"PREG_PATTERN_ORDER">>, 1},
{<<"PREG_SET_ORDER">>, 2},
{<<"PREG_OFFSET_CAPTURE">>, ?PREG_OFFSET_CAPTURE},
{<<"PREG_SPLIT_NO_EMPTY">>, 1},
{<<"PREG_SPLIT_DELIM_CAPTURE">>, 2},
{<<"PREG_SPLIT_OFFSET_CAPTURE">>, 4},
{<<"PREG_NO_ERROR">>, 0},
{<<"PREG_INTERNAL_ERROR">>, 1},
{<<"PREG_BACKTRACK_LIMIT_ERROR">>, 2},
{<<"PREG_RECURSION_LIMIT_ERROR">>, 3},
{<<"PREG_BAD_UTF8_ERROR">>, 4},
{<<"PREG_BAD_UTF8_OFFSET_ERROR">>, 5},
{<<"PCRE_VERSION">>, <<?PCRE_VERSION>>}].
-spec preg_replace(ephp:context_id(),
line(),
Pattern :: var_value(),
Replacement :: var_value(),
Subject :: var_value(),
Limit :: var_value(),
Count :: var_value()) ->
binary().
%% TODO: check when Pattern, Replacement and Subject are arrays
preg_replace(_Context,
_Line,
{_, Pattern},
{_, Replacement},
{_, Subject},
{_, Limit},
{undefined, _}) ->
{RegExp, Flags} = get_parts(Pattern),
PMFlags =
case Limit of
-1 ->
Flags;
_ ->
[{match_limit, Limit} | Flags]
end,
Replace = parse_replace(Replacement),
S = re:replace(Subject, RegExp, Replace, [{return, binary} | PMFlags]),
iolist_to_binary(S).
-spec preg_match(ephp:context_id(),
line(),
Pattern :: var_value(),
Subject :: var_value(),
Matches :: var_value(),
Flags :: var_value(),
Offset :: var_value()) ->
pos_integer().
preg_match(_Context,
_Line,
{_, Pattern},
{_, Subject},
{undefined, _},
{_, _},
{_, Offset}) ->
{RegExp, Flags} = get_parts(Pattern),
PMFlags = [{offset, Offset}],
case re:run(Subject, RegExp, Flags ++ PMFlags) of
{match, _} ->
1;
nomatch ->
0
end;
preg_match(Context,
_Line,
{_, Pattern},
{_, Subject},
{VarMatches, _},
{_, Flag},
{_, Offset}) ->
{RegExp, Flags} = get_parts(Pattern),
PMFlags = [{capture, all, binary}, {offset, Offset}],
ValMatches =
case re:run(Subject, RegExp, Flags ++ PMFlags) of
{match, Matches} ->
if Flag =:= ?PREG_OFFSET_CAPTURE ->
PMFlags2 = [{capture, all, index}, {offset, Offset}],
{match, Idx} = re:run(Subject, RegExp, Flags ++ PMFlags2),
Elements =
lists:zipwith(fun({I, _}, Chunk) -> ephp_array:from_list([Chunk, I]) end,
Idx,
Matches),
ephp_array:from_list(Elements);
true ->
ephp_array:from_list(Matches)
end;
nomatch ->
ephp_array:new()
end,
ephp_context:set(Context, VarMatches, ValMatches),
ephp_array:size(ValMatches).
-spec preg_quote(ephp:context_id(), line(), Str :: var_value(), Delim :: var_value()) ->
binary().
preg_quote(_Context, _Line, {_, Str}, {_, DelimBin}) ->
Delims = binary_to_list(<<".\\+*?[^]$(){}=!<>|:-", DelimBin/binary>>),
escape(Str, <<>>, Delims).
%%------------------------------------------------------------------------------
%% Internal functions
escape(<<>>, Result, _) ->
Result;
escape(<<A/utf8, Str/binary>>, StrRes, Delims) ->
case lists:member(A, Delims) of
true ->
escape(Str, <<StrRes/binary, "\\", A/utf8>>, Delims);
false ->
escape(Str, <<StrRes/binary, A/utf8>>, Delims)
end.
get_parts(<<InitDelim:8, Rest/binary>>) ->
EndDelim = get_end_delimiter(InitDelim),
{RegExp, RawFlags} = parse_regexp(Rest, EndDelim),
Flags = parse_flags(RawFlags),
{RegExp, Flags}.
parse_replace(Replacement) ->
parse_replace(Replacement, <<>>).
parse_replace(<<>>, Result) ->
Result;
parse_replace(<<"\\", Rest/binary>>, Result) ->
parse_replace(Rest, <<Result/binary, "\\\\">>);
parse_replace(<<"${", Rest/binary>>, Result) ->
case binary:split(Rest, <<"}">>) of
[Num, NewRest] ->
parse_replace(NewRest, <<Result/binary, "\\", Num/binary>>);
_ ->
%% TODO: error?
ok
end;
parse_replace(<<"$", Rest/binary>>, Result) ->
{NewRest, Num} = parse_number(Rest, <<>>),
parse_replace(NewRest, <<Result/binary, "\\", Num/binary>>);
parse_replace(<<A/utf8, Rest/binary>>, Result) ->
parse_replace(Rest, <<Result/binary, A/utf8>>).
parse_number(<<A:8, Rest/binary>>, Result) when A >= 0 andalso A =< 9 ->
parse_number(Rest, <<Result/binary, A:8>>);
parse_number(Rest, Result) ->
{Rest, Result}.
parse_regexp(String, EndDelim) ->
parse_regexp(String, <<>>, EndDelim).
parse_regexp(<<>>, _Result, _EndDelim) ->
throw({error, enodelim});
parse_regexp(<<EndDelim:8, Rest/binary>>, Result, EndDelim) ->
{Result, Rest};
parse_regexp(<<"\\", EndDelim:8, Rest/binary>>, Result, EndDelim) ->
parse_regexp(Rest, <<Result/binary, "\\", EndDelim:8>>, EndDelim);
parse_regexp(<<"(", Rest/binary>>, Result, EndDelim) ->
{NewResult, NewRest} = parse_regexp(Rest, $)),
parse_regexp(NewRest, <<Result/binary, "(", NewResult/binary, ")">>, EndDelim);
parse_regexp(<<"[", Rest/binary>>, Result, EndDelim) ->
{NewResult, NewRest} = parse_regexp(Rest, $]),
parse_regexp(NewRest, <<Result/binary, "[", NewResult/binary, "]">>, EndDelim);
parse_regexp(<<"<", Rest/binary>>, Result, EndDelim) ->
{NewResult, NewRest} = parse_regexp(Rest, $>),
parse_regexp(NewRest, <<Result/binary, "<", NewResult/binary, ">">>, EndDelim);
parse_regexp(<<"{", Rest/binary>>, Result, EndDelim) ->
{NewResult, NewRest} = parse_regexp(Rest, $}),
parse_regexp(NewRest, <<Result/binary, "{", NewResult/binary, "}">>, EndDelim);
parse_regexp(<<A:8, Rest/binary>>, Result, EndDelim) ->
parse_regexp(Rest, <<Result/binary, A:8>>, EndDelim).
parse_flags(RawFlags) ->
parse_flags(RawFlags, []).
parse_flags(<<"i", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [caseless | Flags]);
parse_flags(<<"m", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [multiline | Flags]);
parse_flags(<<"s", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [dotall | Flags]);
parse_flags(<<"x", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [extended | Flags]);
parse_flags(<<"A", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [anchored | Flags]);
parse_flags(<<"D", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [dollar_endonly | Flags]);
parse_flags(<<"U", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [ungreedy | Flags]);
parse_flags(<<"J", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [dupnames | Flags]);
parse_flags(<<"u", RestFlags/binary>>, Flags) ->
parse_flags(RestFlags, [unicode, bsr_unicode | Flags]);
parse_flags(<<"X", RestFlags/binary>>, Flags) ->
%% TODO
parse_flags(RestFlags, Flags);
parse_flags(<<"S", RestFlags/binary>>, Flags) ->
%% TODO
parse_flags(RestFlags, Flags);
parse_flags(<<>>, Flags) ->
Flags.
get_end_delimiter($() ->
$);
get_end_delimiter($<) ->
$>;
get_end_delimiter($[) ->
$];
get_end_delimiter(${) ->
$};
get_end_delimiter($\\) ->
throw({error, edelimiter});
get_end_delimiter(A) when ?IS_ALPHA(A) ->
throw({error, edelimiter});
get_end_delimiter(A) when ?IS_NUMBER(A) ->
throw({error, edelimiter});
get_end_delimiter(A) when ?IS_SPACE(A) ->
throw({error, edelimiter});
get_end_delimiter(A) ->
A.