Packages

Compile and evaluate regular expressions using Non-deterministic Finite Automata (NFAs).

Current section

Files

Jump to
rexen src rexen@nfa@thompson.erl
Raw

src/rexen@nfa@thompson.erl

-module(rexen@nfa@thompson).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([to_nfa/1]).
-file("src/rexen/nfa/thompson.gleam", 102).
-spec one_step_stack(
list(rexen@nfa@machine:nfa()),
fun((rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa())
) -> {ok, list(rexen@nfa@machine:nfa())} | {error, binary()}.
one_step_stack(Stack, Func) ->
case Stack of
[] ->
{error, <<"Expected 1 nfa on the stack, got none"/utf8>>};
[Val | Rest] ->
Nfa = Func(Val),
{ok, gleam@list:prepend(Rest, Nfa)}
end.
-file("src/rexen/nfa/thompson.gleam", 115).
-spec two_step_stack(
list(rexen@nfa@machine:nfa()),
fun((rexen@nfa@machine:nfa(), rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa())
) -> {ok, list(rexen@nfa@machine:nfa())} | {error, binary()}.
two_step_stack(Stack, Func) ->
case Stack of
[] ->
{error, <<"Stack is not supposed to be empty"/utf8>>};
[_] ->
{error,
<<"Expected at least 2 nfa's on the stack, got only one"/utf8>>};
[First, Second | Rest] ->
Nfa = Func(Second, First),
{ok, gleam@list:prepend(Rest, Nfa)}
end.
-file("src/rexen/nfa/thompson.gleam", 129).
-spec empty_expr() -> rexen@nfa@machine:nfa().
empty_expr() ->
_pipe = rexen@nfa@machine:new(),
_pipe@1 = rexen@nfa@machine:declare_states(
_pipe,
[<<"q0"/utf8>>, <<"q1"/utf8>>]
),
_pipe@2 = rexen@nfa@machine:set_initial_state(_pipe@1, <<"q0"/utf8>>),
_pipe@3 = rexen@nfa@machine:set_ending_states(_pipe@2, [<<"q1"/utf8>>]),
rexen@nfa@machine:add_transition(
_pipe@3,
<<"q0"/utf8>>,
<<"q1"/utf8>>,
any_matcher
).
-file("src/rexen/nfa/thompson.gleam", 137).
-spec single_char(binary()) -> rexen@nfa@machine:nfa().
single_char(Char) ->
_pipe = rexen@nfa@machine:new(),
_pipe@1 = rexen@nfa@machine:declare_states(
_pipe,
[<<"q0"/utf8>>, <<"q1"/utf8>>]
),
_pipe@2 = rexen@nfa@machine:set_initial_state(_pipe@1, <<"q0"/utf8>>),
_pipe@3 = rexen@nfa@machine:set_ending_states(_pipe@2, [<<"q1"/utf8>>]),
rexen@nfa@machine:add_transition(
_pipe@3,
<<"q0"/utf8>>,
<<"q1"/utf8>>,
{character_matcher, Char}
).
-file("src/rexen/nfa/thompson.gleam", 242).
-spec transition_ending_states(
rexen@nfa@machine:nfa(),
list(binary()),
binary(),
rexen@nfa@state:matcher()
) -> rexen@nfa@machine:nfa().
transition_ending_states(M, From, To, Matcher) ->
case From of
[] ->
M;
[F | Rest] ->
transition_ending_states(
rexen@nfa@machine:add_transition(M, F, To, Matcher),
Rest,
To,
Matcher
)
end.
-file("src/rexen/nfa/thompson.gleam", 317).
-spec update_label(binary(), integer()) -> binary().
update_label(Str, N) ->
_assert_subject = gleam_stdlib:parse_int(gleam@string:drop_start(Str, 1)),
{ok, Number} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"rexen/nfa/thompson"/utf8>>,
function => <<"update_label"/utf8>>,
line => 318})
end,
Label = Number + N,
<<(gleam@string:slice(Str, 0, 1))/binary,
(erlang:integer_to_binary(Label))/binary>>.
-file("src/rexen/nfa/thompson.gleam", 271).
-spec update_ending_state_labels(list(binary()), list(binary()), integer()) -> list(binary()).
update_ending_state_labels(States, Output, N) ->
case States of
[] ->
Output;
[Label | Rest] ->
Out = lists:append(Output, [update_label(Label, N)]),
update_ending_state_labels(Rest, Out, N)
end.
-file("src/rexen/nfa/thompson.gleam", 302).
-spec update_transition_labels(
list({rexen@nfa@state:matcher(), binary()}),
list({rexen@nfa@state:matcher(), binary()}),
integer()
) -> list({rexen@nfa@state:matcher(), binary()}).
update_transition_labels(Transitions, Output, N) ->
case Transitions of
[] ->
Output;
[{Matcher, State} | Rest] ->
New_state = update_label(State, N),
Output@1 = lists:append(Output, [{Matcher, New_state}]),
update_transition_labels(Rest, Output@1, N)
end.
-file("src/rexen/nfa/thompson.gleam", 285).
-spec update_state_labels(
list({binary(), rexen@nfa@state:state()}),
list({binary(), rexen@nfa@state:state()}),
integer()
) -> list({binary(), rexen@nfa@state:state()}).
update_state_labels(States, Output, N) ->
case States of
[] ->
Output;
[{Label, State} | Rest] ->
New_label = update_label(Label, N),
New_transitions = update_transition_labels(
erlang:element(3, State),
[],
N
),
New_state = {state, New_label, New_transitions},
New_out = lists:append(Output, [{New_label, New_state}]),
update_state_labels(Rest, New_out, N)
end.
-file("src/rexen/nfa/thompson.gleam", 261).
-spec update_nfa_labels(rexen@nfa@machine:nfa(), integer()) -> rexen@nfa@machine:nfa().
update_nfa_labels(A, N) ->
New_states = update_state_labels(maps:to_list(erlang:element(2, A)), [], N),
New_ending_states = update_ending_state_labels(erlang:element(4, A), [], N),
{nfa,
maps:from_list(New_states),
update_label(erlang:element(3, A), N),
New_ending_states}.
-file("src/rexen/nfa/thompson.gleam", 145).
-spec zero_or_one(rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa().
zero_or_one(A) ->
Last_state = <<"q"/utf8,
(erlang:integer_to_binary(maps:size(erlang:element(2, A)) + 1))/binary>>,
Subject = update_nfa_labels(A, 1),
_pipe = rexen@nfa@machine:declare_states(
Subject,
[<<"q0"/utf8>>, Last_state]
),
_pipe@1 = transition_ending_states(
_pipe,
erlang:element(4, Subject),
Last_state,
epsilon_matcher
),
_pipe@2 = rexen@nfa@machine:add_transition(
_pipe@1,
<<"q0"/utf8>>,
erlang:element(3, Subject),
epsilon_matcher
),
_pipe@3 = rexen@nfa@machine:add_transition(
_pipe@2,
<<"q0"/utf8>>,
Last_state,
epsilon_matcher
),
_pipe@4 = rexen@nfa@machine:set_initial_state(_pipe@3, <<"q0"/utf8>>),
rexen@nfa@machine:set_ending_states(_pipe@4, [Last_state]).
-file("src/rexen/nfa/thompson.gleam", 160).
-spec one_or_more(rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa().
one_or_more(A) ->
Last_state = <<"q"/utf8,
(erlang:integer_to_binary(maps:size(erlang:element(2, A)) + 1))/binary>>,
Subject = update_nfa_labels(A, 1),
_pipe = rexen@nfa@machine:declare_states(
Subject,
[<<"q0"/utf8>>, Last_state]
),
_pipe@1 = transition_ending_states(
_pipe,
erlang:element(4, Subject),
erlang:element(3, Subject),
epsilon_matcher
),
_pipe@2 = transition_ending_states(
_pipe@1,
erlang:element(4, Subject),
Last_state,
epsilon_matcher
),
_pipe@3 = rexen@nfa@machine:add_transition(
_pipe@2,
<<"q0"/utf8>>,
erlang:element(3, Subject),
epsilon_matcher
),
_pipe@4 = rexen@nfa@machine:set_initial_state(_pipe@3, <<"q0"/utf8>>),
rexen@nfa@machine:set_ending_states(_pipe@4, [Last_state]).
-file("src/rexen/nfa/thompson.gleam", 179).
-spec closure(rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa().
closure(A) ->
Last_state = <<"q"/utf8,
(erlang:integer_to_binary(maps:size(erlang:element(2, A)) + 1))/binary>>,
Subject = update_nfa_labels(A, 1),
_pipe = rexen@nfa@machine:declare_states(
Subject,
[<<"q0"/utf8>>, Last_state]
),
_pipe@1 = transition_ending_states(
_pipe,
erlang:element(4, Subject),
erlang:element(3, Subject),
epsilon_matcher
),
_pipe@2 = transition_ending_states(
_pipe@1,
erlang:element(4, Subject),
Last_state,
epsilon_matcher
),
_pipe@3 = rexen@nfa@machine:add_transition(
_pipe@2,
<<"q0"/utf8>>,
erlang:element(3, Subject),
epsilon_matcher
),
_pipe@4 = rexen@nfa@machine:add_transition(
_pipe@3,
<<"q0"/utf8>>,
Last_state,
epsilon_matcher
),
_pipe@5 = rexen@nfa@machine:set_initial_state(_pipe@4, <<"q0"/utf8>>),
rexen@nfa@machine:set_ending_states(_pipe@5, [Last_state]).
-file("src/rexen/nfa/thompson.gleam", 199).
-spec concat(rexen@nfa@machine:nfa(), rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa().
concat(A, B) ->
New_b = update_nfa_labels(B, maps:size(erlang:element(2, A))),
_pipe = {nfa,
maps:merge(erlang:element(2, A), erlang:element(2, New_b)),
erlang:element(3, A),
erlang:element(4, New_b)},
transition_ending_states(
_pipe,
erlang:element(4, A),
erlang:element(3, New_b),
epsilon_matcher
).
-file("src/rexen/nfa/thompson.gleam", 213).
-spec union(rexen@nfa@machine:nfa(), rexen@nfa@machine:nfa()) -> rexen@nfa@machine:nfa().
union(A, B) ->
Size_a = maps:size(erlang:element(2, A)),
Size_b = maps:size(erlang:element(2, B)),
Last_state = update_label(<<"q0"/utf8>>, (1 + Size_a) + Size_b),
New_a = update_nfa_labels(A, 1),
New_b = update_nfa_labels(B, 1 + Size_a),
_pipe = {nfa,
maps:merge(erlang:element(2, New_a), erlang:element(2, New_b)),
<<""/utf8>>,
[]},
_pipe@1 = rexen@nfa@machine:declare_states(
_pipe,
[<<"q0"/utf8>>, Last_state]
),
_pipe@2 = rexen@nfa@machine:set_initial_state(_pipe@1, <<"q0"/utf8>>),
_pipe@3 = rexen@nfa@machine:set_ending_states(_pipe@2, [Last_state]),
_pipe@4 = rexen@nfa@machine:add_transition(
_pipe@3,
<<"q0"/utf8>>,
erlang:element(3, New_a),
epsilon_matcher
),
_pipe@5 = rexen@nfa@machine:add_transition(
_pipe@4,
<<"q0"/utf8>>,
erlang:element(3, New_b),
epsilon_matcher
),
_pipe@6 = transition_ending_states(
_pipe@5,
erlang:element(4, New_a),
Last_state,
epsilon_matcher
),
transition_ending_states(
_pipe@6,
erlang:element(4, New_b),
Last_state,
epsilon_matcher
).
-file("src/rexen/nfa/thompson.gleam", 22).
-spec to_nfa_loop(list(rexen@grammar:token()), list(rexen@nfa@machine:nfa())) -> {ok,
rexen@nfa@machine:nfa()} |
{error, binary()}.
to_nfa_loop(Input, Stack) ->
case Input of
[] ->
case gleam@list:first(Stack) of
{ok, Nfa} ->
{ok, Nfa};
{error, _} ->
{error, <<""/utf8>>}
end;
[Tok | Rest] ->
case Tok of
{letter, Char} ->
Nfa@1 = single_char(Char),
to_nfa_loop(Rest, gleam@list:prepend(Stack, Nfa@1));
{operator, Variant} ->
case Variant of
{asterix, _} ->
case one_step_stack(Stack, fun closure/1) of
{error, Err} ->
{error,
<<Err/binary,
". Hint: closure requires 1 preceding character (ie. a*)"/utf8>>};
{ok, New_stack} ->
to_nfa_loop(Rest, New_stack)
end;
{q_mark, _} ->
case one_step_stack(Stack, fun zero_or_one/1) of
{error, Err@1} ->
{error,
<<Err@1/binary,
". Hint: zero or one (QMark) requires 1 preceding
character (ie. a?)"/utf8>>};
{ok, New_stack@1} ->
to_nfa_loop(Rest, New_stack@1)
end;
{plus, _} ->
case one_step_stack(Stack, fun one_or_more/1) of
{error, Err@2} ->
{error,
<<Err@2/binary,
". Hint: one or more (Plus) requires 1 preceding
character (ie. a+)"/utf8>>};
{ok, New_stack@2} ->
to_nfa_loop(Rest, New_stack@2)
end;
{dot, _} ->
case two_step_stack(Stack, fun concat/2) of
{error, Err@3} ->
{error,
<<Err@3/binary,
". Hint: concatenation '?' requires 2 characters (ie. a?b)"/utf8>>};
{ok, New_stack@3} ->
to_nfa_loop(Rest, New_stack@3)
end;
{bar, _} ->
case two_step_stack(Stack, fun union/2) of
{error, Err@4} ->
{error,
<<Err@4/binary,
". Hint: union '+' requires 2 characters (ie. a+b)"/utf8>>};
{ok, New_stack@4} ->
to_nfa_loop(Rest, New_stack@4)
end;
_ ->
{error,
<<"Trailing opening bracket found in the regular expression."/utf8>>}
end;
_ ->
{error,
<<"Trailing closing bracket found in the regular expression."/utf8>>}
end
end.
-file("src/rexen/nfa/thompson.gleam", 11).
-spec to_nfa(list(rexen@grammar:token())) -> {ok, rexen@nfa@machine:nfa()} |
{error, binary()}.
to_nfa(Input) ->
case Input =:= [] of
true ->
{ok, closure(empty_expr())};
false ->
to_nfa_loop(Input, [])
end.