Skip to content

Commit 812b1cd

Browse files
whatyouhideJosé Valim
authored andcommitted
Remove use of Regex in the Version.Parser module (#6077)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent c6f7958 commit 812b1cd

File tree

2 files changed

+76
-43
lines changed

2 files changed

+76
-43
lines changed

lib/elixir/lib/version.ex

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -396,67 +396,92 @@ defmodule Version do
396396
Enum.filter(Enum.reverse(acc), &(&1 != :' '))
397397
end
398398

399-
@version_regex ~r/^
400-
(\d+) # major
401-
(?:\.(\d+))? # minor
402-
(?:\.(\d+))? # patch
403-
(?:\-([\d\w\.\-]+))? # pre
404-
(?:\+([\d\w\.\-]+))? # build
405-
$/x
406-
407399
@spec parse_requirement(String.t) :: {:ok, term} | :error
408400
def parse_requirement(source) do
409401
lexed = lexer(source, [])
410402
to_matchspec(lexed)
411403
end
412404

413-
defp nillify(""), do: nil
414-
defp nillify(o), do: o
415-
416405
@spec parse_version(String.t) :: {:ok, Version.matchable} | :error
417406
def parse_version(string, approximate? \\ false) when is_binary(string) do
418-
if parsed = Regex.run(@version_regex, string) do
419-
destructure [_, major, minor, patch, pre], parsed
420-
patch = nillify(patch)
421-
pre = nillify(pre)
422-
423-
if is_nil(minor) or (is_nil(patch) and not approximate?) do
424-
:error
425-
else
426-
major = String.to_integer(major)
427-
minor = String.to_integer(minor)
428-
patch = patch && String.to_integer(patch)
429-
430-
case parse_pre(pre) do
431-
{:ok, pre} ->
432-
{:ok, {major, minor, patch, pre}}
433-
:error ->
434-
:error
435-
end
436-
end
407+
destructure [version_with_pre, build], String.split(string, "+", parts: 2)
408+
destructure [version, pre], String.split(version_with_pre, "-", parts: 2)
409+
destructure [major, minor, patch], String.split(version, ".")
410+
411+
with {:ok, major} <- require_digits(major),
412+
{:ok, minor} <- require_digits(minor),
413+
{:ok, patch} <- maybe_patch(patch, approximate?),
414+
{:ok, pre_parts} <- optional_dot_separated(pre),
415+
{:ok, pre_parts} <- convert_parts_to_integer(pre_parts, []),
416+
{:ok, _build_parts} <- optional_dot_separated(build) do
417+
{:ok, {major, minor, patch, pre_parts}}
437418
else
438-
:error
419+
_other -> :error
439420
end
440421
end
441422

442-
defp parse_pre(nil), do: {:ok, []}
443-
defp parse_pre(pre), do: parse_pre(String.split(pre, "."), [])
423+
defp require_digits(nil), do: :error
424+
defp require_digits(string) do
425+
if leading_zero?(string), do: :error, else: parse_digits(string, "")
426+
end
444427

445-
defp parse_pre([piece | t], acc) do
446-
cond do
447-
piece =~ ~r/^(0|[1-9][0-9]*)$/ ->
448-
parse_pre(t, [String.to_integer(piece) | acc])
449-
piece =~ ~r/^[0-9]*$/ ->
450-
:error
451-
true ->
452-
parse_pre(t, [piece | acc])
428+
defp leading_zero?(<<?0, _, _::binary>>), do: true
429+
defp leading_zero?(_), do: false
430+
431+
defp parse_digits(<<char, rest::binary>>, acc) when char in ?0..?9,
432+
do: parse_digits(rest, <<acc::binary, char>>)
433+
defp parse_digits(<<>>, acc) when byte_size(acc) > 0,
434+
do: {:ok, String.to_integer(acc)}
435+
defp parse_digits(_, _acc),
436+
do: :error
437+
438+
defp maybe_patch(patch, approximate?)
439+
defp maybe_patch(nil, true), do: {:ok, nil}
440+
defp maybe_patch(patch, _), do: require_digits(patch)
441+
442+
defp optional_dot_separated(nil), do: {:ok, []}
443+
defp optional_dot_separated(string) do
444+
parts = String.split(string, ".")
445+
if Enum.all?(parts, &(&1 != "" and valid_identifier?(&1))) do
446+
{:ok, parts}
447+
else
448+
:error
453449
end
454450
end
455451

456-
defp parse_pre([], acc) do
452+
defp convert_parts_to_integer([part | rest], acc) do
453+
case parse_digits(part, "") do
454+
{:ok, integer} ->
455+
if leading_zero?(part) do
456+
:error
457+
else
458+
convert_parts_to_integer(rest, [integer | acc])
459+
end
460+
:error ->
461+
convert_parts_to_integer(rest, [part | acc])
462+
end
463+
end
464+
465+
defp convert_parts_to_integer([], acc) do
457466
{:ok, Enum.reverse(acc)}
458467
end
459468

469+
defp valid_identifier?(<<char, rest::binary>>)
470+
when char in ?0..?9
471+
when char in ?a..?z
472+
when char in ?A..?Z
473+
when char == ?- do
474+
valid_identifier?(rest)
475+
end
476+
477+
defp valid_identifier?(<<>>) do
478+
true
479+
end
480+
481+
defp valid_identifier?(_other) do
482+
false
483+
end
484+
460485
defp valid_requirement?([]), do: false
461486
defp valid_requirement?([a | next]), do: valid_requirement?(a, next)
462487

lib/elixir/test/elixir/version_test.exs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,17 @@ defmodule VersionTest do
6666
assert {:ok, %V{major: 1, minor: 4, patch: 5, pre: ["6-g3318bd5"]}} = V.parse("1.4.5-6-g3318bd5+ignore")
6767

6868
assert :error = V.parse("foobar")
69-
assert :error = V.parse("2.3")
7069
assert :error = V.parse("2")
70+
assert :error = V.parse("2.")
71+
assert :error = V.parse("2.3")
72+
assert :error = V.parse("2.3.")
73+
assert :error = V.parse("2.3.0-")
74+
assert :error = V.parse("2.3.0+")
7175
assert :error = V.parse("2.3.0-01")
76+
assert :error = V.parse("2.3.00-1")
77+
assert :error = V.parse("2.3.00")
78+
assert :error = V.parse("2.03.0")
79+
assert :error = V.parse("02.3.0")
7280
end
7381

7482
test "to_string" do

0 commit comments

Comments
 (0)