From 02ed41308755caf175ac5ab43614666e4d5ec52b Mon Sep 17 00:00:00 2001 From: Marc Worrell Date: Thu, 19 Jun 2025 11:06:49 +0200 Subject: [PATCH 1/6] Test on newer OTP versions --- .github/workflows/test.yml | 2 +- src/dispatch_compiler.erl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca85721..f6d7a37 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - otp_version: [22,23,24] + otp_version: [25,26,27,28] os: [ubuntu-latest] container: diff --git a/src/dispatch_compiler.erl b/src/dispatch_compiler.erl index f8da499..52b82d0 100644 --- a/src/dispatch_compiler.erl +++ b/src/dispatch_compiler.erl @@ -363,6 +363,7 @@ list_bindings([P|Ps], Nr, Acc) -> -spec binding_var(Variable) -> Result when Variable :: {Name, any()} | {Name, term(), term()} | term(), + Name :: atom() | binary() | integer() | list(), Result :: erl_syntax:syntaxTree(). binding_var({Name, _}) -> erl_syntax:atom(to_atom(Name)); From eb873cff0c621acf2a3932785dbb0efd830269f4 Mon Sep 17 00:00:00 2001 From: Marc Worrell Date: Thu, 19 Jun 2025 11:08:02 +0200 Subject: [PATCH 2/6] Remove OTP25 test --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f6d7a37..7dd89a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - otp_version: [25,26,27,28] + otp_version: [26,27,28] os: [ubuntu-latest] container: From 998a852b1558e22cb0b2402fd46759784896d038 Mon Sep 17 00:00:00 2001 From: Marc Worrell Date: Thu, 19 Jun 2025 12:14:14 +0200 Subject: [PATCH 3/6] Use persistent_term to store the pre-compiled regexps --- src/dispatch_compiler.erl | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/dispatch_compiler.erl b/src/dispatch_compiler.erl index 52b82d0..cde86f1 100644 --- a/src/dispatch_compiler.erl +++ b/src/dispatch_compiler.erl @@ -417,17 +417,44 @@ compile_re_path([], Acc) -> compile_re_path([{Token, {Mod, Fun}}|Rest], Acc) -> compile_re_path(Rest, [{Token, {Mod,Fun}}|Acc]); compile_re_path([{Token, RE}|Rest], Acc) -> - {ok, MP} = re:compile(RE), - compile_re_path(Rest, [{Token, MP}|Acc]); + REKey = regexp_compile(RE, []), + compile_re_path(Rest, [{Token, REKey}|Acc]); compile_re_path([{Token, RE, Options}|Rest], Acc) -> {CompileOpt,RunOpt} = lists:partition(fun is_compile_opt/1, Options), - {ok, MP} = re:compile(RE, CompileOpt), - compile_re_path(Rest, [{Token, MP, RunOpt}|Acc]); + REKey = regexp_compile(RE, CompileOpt), + compile_re_path(Rest, [{Token, REKey, RunOpt}|Acc]); compile_re_path([Token|Rest], Acc) when is_list(Token) -> compile_re_path(Rest, [unicode:characters_to_binary(Token)|Acc]); compile_re_path([Token|Rest], Acc) when is_atom(Token); is_binary(Token) -> compile_re_path(Rest, [Token|Acc]). +%% @doc Compile the regexp and store in the persistent_term for quick access. +%% Doing so allows the regexp to be compiled only once and reused. + +-spec regexp_compile(RE, Options) -> {ok, REKey} when + RE :: string() | binary(), + Options :: re:compile_options(), + REKey :: term(). +regexp_compile(RE, CompileOpt) -> + Key = {RE, CompileOpt}, + REKey = {dispatch_re_pattern, Key}, + case persistent_term:get(REKey, undefined) of + undefined -> + {ok, MP} = re:compile(RE, CompileOpt), + persistent_term:put(REKey, MP), + REKey; + MP when is_tuple(MP), element(1, MP) =:= re_pattern -> + REKey + end. + +%% @doc Run the regular expression over the path element. +%% The regular expression is a key into the persistent_term storage, which +%% is defined when compiling the dispatch rule with the regular expression. + +regexp_run(Match, {dispatch_re_pattern, _} = REKey, RunOpt) -> + RE = persistent_term:get(REKey), + re:run(Match, RE, RunOpt). + %% @doc Only allow options valid for the `re:compile/3' function. -spec is_compile_opt(CompileOption) -> Result when @@ -555,12 +582,12 @@ bind([{Token, Fun}|RestToken], [Match|RestMatch], Bindings, Context) when is_fun {ok, Value} -> bind(RestToken, RestMatch, [{Token, Value}|Bindings], Context) end; bind([{Token, RegExp}|RestToken], [Match|RestMatch], Bindings, Context) when is_atom(Token) -> - case re:run(Match, RegExp) of + case regexp_run(Match, RegExp, []) of {match, _} -> bind(RestToken, RestMatch, [{Token, Match}|Bindings], Context); nomatch -> fail end; bind([{Token, RegExp, Options}|RestToken], [Match|RestMatch], Bindings, Context) when is_atom(Token) -> - case re:run(Match, RegExp, Options) of + case regexp_run(Match, RegExp, Options) of {match, []} -> bind(RestToken, RestMatch, [{Token, Match}|Bindings], Context); {match, [T|_]} when is_tuple(T) -> bind(RestToken, RestMatch, [{Token, Match}|Bindings], Context); {match, [Captured]} -> bind(RestToken, RestMatch, [{Token, Captured}|Bindings], Context); From af6f4d7a5972a797334523610c23155200799792 Mon Sep 17 00:00:00 2001 From: Marc Worrell Date: Thu, 19 Jun 2025 12:41:39 +0200 Subject: [PATCH 4/6] Fix types --- src/dispatch_compiler.app.src | 2 +- src/dispatch_compiler.erl | 120 ++++++++++++++++------------------ 2 files changed, 57 insertions(+), 65 deletions(-) diff --git a/src/dispatch_compiler.app.src b/src/dispatch_compiler.app.src index e531589..61a47e4 100644 --- a/src/dispatch_compiler.app.src +++ b/src/dispatch_compiler.app.src @@ -3,7 +3,7 @@ {description, "Compile dispatch rules to an Erlang module"}, {vsn, "git"}, {registered, []}, - {applications, [ kernel, stdlib ]}, + {applications, [ kernel, stdlib, syntax_tools, compiler ]}, {env, []}, {maintainers, ["Zotonic Team"]}, {licenses, ["Apache 2.0"]}, diff --git a/src/dispatch_compiler.erl b/src/dispatch_compiler.erl index cde86f1..148c6c4 100644 --- a/src/dispatch_compiler.erl +++ b/src/dispatch_compiler.erl @@ -1,6 +1,6 @@ %% @author Marc Worrell %% @copyright 2015 Marc Worrell -%% @doc +%% @doc %% Compile dispatch rules to an Erlang module. %% %% The dispatch compiler takes all the list of dispatch rules and creates @@ -39,7 +39,7 @@ %% match1(Path, Context). %% %% match1(..., Context) -> -%% ... +%% ... %% match1(_, _Context) -> %% fail. %% ''' @@ -50,9 +50,9 @@ %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at -%% +%% %% http://www.apache.org/licenses/LICENSE-2.0 -%% +%% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -74,13 +74,13 @@ -type dispatch_rule_path() :: list(dispatch_rule_token()). --type dispatch_rule_token() :: +-type dispatch_rule_token() :: binary() | '*' - | atom() + | atom() | {atom(), {atom(), atom()}} | {atom(), {atom(), atom(), list()}} - | {atom(), match_function()} + | {atom(), match_function()} | {atom(), RegExp::iodata() | unicode:charlist()} | {atom(), RegExp::iodata() | unicode:charlist(), Options::list()}. @@ -90,9 +90,9 @@ | fun((binary()) -> boolean() | {ok, any()}). -export_type([ - dispatch_rule/0, - dispatch_rule_path/0, - dispatch_rule_token/0, + dispatch_rule/0, + dispatch_rule_path/0, + dispatch_rule_token/0, match_function/0, binding/0 ]). @@ -100,7 +100,7 @@ %% @doc Compile and load Erlang module. -spec compile_load(ModuleName, DLs) -> Result when - ModuleName :: atom(), + ModuleName :: atom(), DLs :: [dispatch_rule()], Result :: ok. compile_load(ModuleName, DLs) -> @@ -129,9 +129,9 @@ compile(ModuleName, DLs) when is_atom(ModuleName), is_list(DLs) -> %% @equiv Module:match(Tokens, Context) %% @doc Launch `Module' `match' function. --spec match(Module, Tokens, Context) -> Result when - Module :: atom(), - Tokens :: [binary()], +-spec match(Module, Tokens, Context) -> Result when + Module :: atom(), + Tokens :: [binary()], Context :: any(), Result :: {ok, {dispatch_rule(), [binding()]}} | fail. match(Module, Tokens, Context) -> @@ -166,7 +166,7 @@ match_asts(DLs, FunAsts, Nr) -> %% matches left, in that case a chained match function will be added by match_asts/3 -spec match_ast(DLs, Nr) -> Result when - DLs :: [dispatch_rule()], + DLs :: [dispatch_rule()], Nr :: non_neg_integer(), Result :: {ok, DLs, SyntaxTree}, SyntaxTree :: erl_syntax:syntaxTree(). @@ -181,8 +181,8 @@ funname(0) -> match; funname(N) -> list_to_atom("match"++integer_to_list(N)). -spec match_clauses(DLs, Nr, Acc) -> Result when - DLs :: [] | [dispatch_rule()], - Nr :: non_neg_integer(), + DLs :: [] | [dispatch_rule()], + Nr :: non_neg_integer(), Acc :: [erl_syntax:syntaxTree()], Result :: {ok, DLs, [erl_syntax:syntaxTree()]}. match_clauses([], _Nr, Acc) -> @@ -238,7 +238,7 @@ match_clauses([ DispatchRule | DLs ], Nr, Acc) -> erl_syntax:abstract(DispatchRule), erl_syntax:variable("Bindings") ]) - ]) + ]) ]), % One of: % * fail -> matchN(Pattern, Context) @@ -246,7 +246,7 @@ match_clauses([ DispatchRule | DLs ], Nr, Acc) -> erl_syntax:clause( [ erl_syntax:atom(fail) ], none, - [ + [ case IsMatchingOther of true -> erl_syntax:application( @@ -293,7 +293,7 @@ list_pattern(Pattern) -> list_pattern(Pattern, 1, []). -spec list_pattern(Pattern, Nr, Acc) -> Result when - Pattern :: [] | ['*'] | [term()], + Pattern :: [ '*' | term() ], Nr :: non_neg_integer(), Acc :: [erl_syntax:syntaxTree()], Result :: erl_syntax:syntaxTree(). @@ -334,16 +334,16 @@ list_pattern([_|Ps], Nr, Acc) -> %% @equiv list_bindings(Pattern, 1, []) -spec list_bindings(Pattern) -> Result when - Pattern :: [] | ['*'] | [term()], - Result :: [erl_syntax:syntaxTree()]. + Pattern :: [ '*' | term() ], + Result :: erl_syntax:syntaxTree(). list_bindings(Pattern) -> list_bindings(Pattern, 1, []). -spec list_bindings(Pattern, Nr, Acc) -> Result when - Pattern :: [] | ['*'] | [term()], + Pattern :: [ '*' | term() ], Nr :: integer(), Acc :: [erl_syntax:syntaxTree()], - Result :: [erl_syntax:syntaxTree()]. + Result :: erl_syntax:syntaxTree(). list_bindings([], _Nr, Acc) -> erl_syntax:list(lists:reverse(Acc), none); list_bindings([B|Ps], Nr, Acc) when is_binary(B); is_list(B) -> @@ -374,11 +374,11 @@ binding_var(Name) -> -spec to_atom(Name) -> Result when Name :: atom() | binary() | integer() | list(), - Result :: erl_syntax:syntaxTree(). + Result :: atom(). to_atom(Name) when is_atom(Name) -> Name; -to_atom(Name) when is_binary(Name) -> list_to_atom(binary_to_list(Name)); -to_atom(Name) when is_integer(Name) -> list_to_atom(integer_to_list(Name)); -to_atom(Name) when is_list(Name) -> list_to_atom(Name). +to_atom(Name) when is_binary(Name) -> list_to_atom(binary_to_list(Name)); +to_atom(Name) when is_integer(Name) -> list_to_atom(integer_to_list(Name)); +to_atom(Name) when is_list(Name) -> list_to_atom(Name). -spec is_simple_pattern(Pattern) -> Result when Pattern :: [] | [term()], @@ -390,27 +390,18 @@ is_simple_pattern([z_language|_]) -> false; is_simple_pattern([V|Ps]) when is_atom(V) -> is_simple_pattern(Ps); is_simple_pattern(_) -> false. --spec compile_re_path(TokenList, Acc) -> Result when - TokenList :: [{Token, {Mod, Fun}}] | [{Token, RE}] | [{Token, RE, CompileOptions}], - Token :: atom(), - Mod :: atom(), - Fun :: atom(), - RE :: iodata() | unicode:charlist(), - CompileOptions :: [CompileOption], - CompileOption :: unicode | anchored | caseless | dotall | - extended | ungreedy | no_auto_capture | dupnames, - Acc :: [{Token, MP}] | [{Token, MP, RunOptions}], - MP :: re:mp(), - RunOptions :: [RunOption], - RunOption :: anchored | global | notbol | noteol | notempty | - notempty_atstart | report_errors | {offset, non_neg_integer()} | - {match_limit, non_neg_integer()} | {match_limit_recursion, non_neg_integer()} | - {newline, re:nl_spec()} | bsr_anycrlf | bsr_unicode | {capture, ValueSpec} | - {capture, ValueSpec, Type} | CompileOption, - ValueSpec :: all | all_but_first | all_names | first | none | ValueList, - ValueList :: [ValueID], - ValueID :: integer() | string() | atom(), - Type :: index | list | binary, +-spec compile_re_path(PathTokens, Acc) -> Result when + PathTokens :: dispatch_rule_path(), + Acc :: [ MatchToken ], + MatchToken :: {Binding, {Mod, Fun}} + | {Binding, RE} + | {Binding, RE, re:options()} + | Binding + | binary(), + Mod :: module(), + Fun :: atom(), + Binding :: atom(), + RE :: {dispatch_re_pattern, term()}, Result :: Acc. compile_re_path([], Acc) -> lists:reverse(Acc); @@ -431,7 +422,7 @@ compile_re_path([Token|Rest], Acc) when is_atom(Token); is_binary(Token) -> %% @doc Compile the regexp and store in the persistent_term for quick access. %% Doing so allows the regexp to be compiled only once and reused. --spec regexp_compile(RE, Options) -> {ok, REKey} when +-spec regexp_compile(RE, Options) -> REKey when RE :: string() | binary(), Options :: re:compile_options(), REKey :: term(). @@ -458,7 +449,8 @@ regexp_run(Match, {dispatch_re_pattern, _} = REKey, RunOpt) -> %% @doc Only allow options valid for the `re:compile/3' function. -spec is_compile_opt(CompileOption) -> Result when - CompileOption :: unicode | anchored | caseless | dotall | extended | ungreedy | no_auto_capture | dupnames, + CompileOption :: unicode | anchored | caseless | dotall | extended | ungreedy + | no_auto_capture | dupnames, Result :: boolean(). is_compile_opt(unicode) -> true; is_compile_opt(anchored) -> true; @@ -477,8 +469,8 @@ var(Nr) -> erl_syntax:variable("V"++integer_to_list(Nr)). -spec is_matching_other(Pattern, DLs) -> Result when - Pattern :: [] | ['*'] | [binary()] | [list()] | [term()], - DLs :: [dispatch_rule()], + Pattern :: [] | ['*'] | [binary()] | [list()] | [term()], + DLs :: [dispatch_rule()], Result :: boolean(). is_matching_other(_Pattern, []) -> false; @@ -489,18 +481,18 @@ is_matching_other(Pattern0, [{_Name, Pattern1, _Controller, _Options}|DLs]) -> end. -spec is_match(Pattern1, Pattern2) -> Result when - Pattern1 :: [] | ['*'] | [binary()] | [list()] | [term()], + Pattern1 :: [] | ['*'] | [binary()] | [list()] | [term()], Pattern2 :: Pattern1, Result :: boolean(). -is_match([], []) -> +is_match([], []) -> true; is_match(['*'], _) -> true; is_match(_, ['*']) -> true; -is_match([], _) -> +is_match([], _) -> false; -is_match(_, []) -> +is_match(_, []) -> false; is_match([B|Pattern0], [B|Pattern1]) -> is_match(Pattern0, Pattern1); @@ -518,17 +510,17 @@ is_match([_|Pattern0], [_|Pattern1]) -> %% @doc Runtime callback for argument binding with checks on the arguments. The checks %% can be a function, module-function, or regexp. --spec runtime_bind(Pattern, Path, Context) -> Result when - Pattern :: list(), - Path :: [binary()], +-spec runtime_bind(Pattern, Path, Context) -> Result when + Pattern :: list(), + Path :: [binary()], Context :: any(), Result :: {ok, [binding()]} | fail. runtime_bind(Pattern, Path, Context) -> bind(Pattern, Path, [], Context). -spec bind(Pattern, Path, Bindings, Context) -> Result when - Pattern :: [] | ['*'] | [Token| RestToken] | - [{Token, {Module,Function}}|RestToken] | + Pattern :: [] | ['*'] | [Token| RestToken] | + [{Token, {Module,Function}}|RestToken] | [{Token, {Module,Function,Args}}|RestToken] | [{Token, Fun}|RestToken] | [{Token, RegExp}|RestToken] | term(), Token :: atom(), @@ -555,14 +547,14 @@ bind([Token|RestToken], [Token|RestMatch], Bindings, Context) -> bind(RestToken, RestMatch, Bindings, Context); bind([Token|RestToken], [Match|RestMatch], Bindings, Context) when is_atom(Token) -> bind(RestToken, RestMatch, [{Token, Match}|Bindings], Context); -bind([{Token, {Module,Function}}|RestToken],[Match|RestMatch],Bindings, Context) +bind([{Token, {Module,Function}}|RestToken],[Match|RestMatch],Bindings, Context) when is_atom(Token), is_atom(Module), is_atom(Function) -> case Module:Function(Match, Context) of true -> bind(RestToken, RestMatch, [{Token, Match}|Bindings], Context); false -> fail; {ok, Value} -> bind(RestToken, RestMatch, [{Token, Value}|Bindings], Context) end; -bind([{Token, {Module,Function,Args}}|RestToken],[Match|RestMatch],Bindings, Context) +bind([{Token, {Module,Function,Args}}|RestToken],[Match|RestMatch],Bindings, Context) when is_atom(Token), is_atom(Module), is_atom(Function), is_list(Args) -> case erlang:apply(Module, Function, [Match,Context|Args]) of true -> bind(RestToken, RestMatch, [{Token, Match}|Bindings], Context); From 9a0e5664cfc4f503ffa14f50e1a436a2c6147651 Mon Sep 17 00:00:00 2001 From: Marc Worrell Date: Thu, 19 Jun 2025 12:52:42 +0200 Subject: [PATCH 5/6] Fix types --- src/dispatch_compiler.erl | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/dispatch_compiler.erl b/src/dispatch_compiler.erl index 148c6c4..9f7c36d 100644 --- a/src/dispatch_compiler.erl +++ b/src/dispatch_compiler.erl @@ -82,13 +82,38 @@ | {atom(), {atom(), atom(), list()}} | {atom(), match_function()} | {atom(), RegExp::iodata() | unicode:charlist()} - | {atom(), RegExp::iodata() | unicode:charlist(), Options::list()}. + | {atom(), RegExp::iodata() | unicode:charlist(), Options::re_options()}. -type binding() :: {atom(), binary() | list(binary() | any())}. -type match_function() :: fun((binary(), any()) -> boolean() | {ok, any()}) | fun((binary()) -> boolean() | {ok, any()}). +% For compat with OTP26, where the re module does not export the options. +% This can be removed when the minimum supported OTP version is 27. +-type re_nl_spec() :: cr | crlf | lf | nul | anycrlf | any. + +-type re_compile_options() :: [re_compile_option()]. +-type re_compile_option() :: unicode | anchored | caseless | dollar_endonly + | dotall | extended | firstline | multiline + | no_auto_capture | dupnames | ungreedy + | {newline, re_nl_spec()} + | bsr_anycrlf | bsr_unicode + | no_start_optimize | ucp | never_utf. + +-type re_options() :: [re_option()]. +-type re_option() :: anchored | global | notbol | noteol | notempty | + notempty_atstart | report_errors | + {offset, non_neg_integer()} | + {match_limit, non_neg_integer()} | + {match_limit_recursion, non_neg_integer()} | + {capture, ValueSpec :: re_capture()} | + {capture, ValueSpec :: re_capture(), Type :: index | list | binary} | + re_compile_option(). +-type re_capture() :: all | all_but_first | all_names | first | none | + ValueList :: [integer() | string() | atom()]. + + -export_type([ dispatch_rule/0, dispatch_rule_path/0, @@ -394,23 +419,27 @@ is_simple_pattern(_) -> false. PathTokens :: dispatch_rule_path(), Acc :: [ MatchToken ], MatchToken :: {Binding, {Mod, Fun}} + | {Binding, {Mod, Fun, Args}} | {Binding, RE} | {Binding, RE, re:options()} | Binding | binary(), Mod :: module(), Fun :: atom(), + Args :: list(), Binding :: atom(), RE :: {dispatch_re_pattern, term()}, Result :: Acc. compile_re_path([], Acc) -> lists:reverse(Acc); -compile_re_path([{Token, {Mod, Fun}}|Rest], Acc) -> +compile_re_path([{Token, {Mod, Fun}}|Rest], Acc) when is_atom(Mod), is_atom(Fun) -> compile_re_path(Rest, [{Token, {Mod,Fun}}|Acc]); -compile_re_path([{Token, RE}|Rest], Acc) -> +compile_re_path([{Token, {Mod, Fun, Args}}|Rest], Acc) when is_atom(Mod), is_atom(Fun), is_list(Args) -> + compile_re_path(Rest, [{Token, {Mod,Fun,Args}}|Acc]); +compile_re_path([{Token, RE}|Rest], Acc) when is_list(RE); is_binary(RE) -> REKey = regexp_compile(RE, []), compile_re_path(Rest, [{Token, REKey}|Acc]); -compile_re_path([{Token, RE, Options}|Rest], Acc) -> +compile_re_path([{Token, RE, Options}|Rest], Acc) when is_list(RE); is_binary(RE) -> {CompileOpt,RunOpt} = lists:partition(fun is_compile_opt/1, Options), REKey = regexp_compile(RE, CompileOpt), compile_re_path(Rest, [{Token, REKey, RunOpt}|Acc]); @@ -424,7 +453,7 @@ compile_re_path([Token|Rest], Acc) when is_atom(Token); is_binary(Token) -> -spec regexp_compile(RE, Options) -> REKey when RE :: string() | binary(), - Options :: re:compile_options(), + Options :: re_compile_options(), REKey :: term(). regexp_compile(RE, CompileOpt) -> Key = {RE, CompileOpt}, From b6eacd439985af96ba89c9794a8968c2b3ae9b83 Mon Sep 17 00:00:00 2001 From: Marc Worrell Date: Thu, 19 Jun 2025 12:56:01 +0200 Subject: [PATCH 6/6] re options type --- src/dispatch_compiler.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatch_compiler.erl b/src/dispatch_compiler.erl index 9f7c36d..7e20e6e 100644 --- a/src/dispatch_compiler.erl +++ b/src/dispatch_compiler.erl @@ -421,7 +421,7 @@ is_simple_pattern(_) -> false. MatchToken :: {Binding, {Mod, Fun}} | {Binding, {Mod, Fun, Args}} | {Binding, RE} - | {Binding, RE, re:options()} + | {Binding, RE, re_options()} | Binding | binary(), Mod :: module(),