عبارات باقاعده (رجکس) ابزاری قدرتمند برای کار با رشتهها در Elixir هستند. عبارتهای باقاعده در Elixir از مشخصات PCRE (Perl Compatible Regular Expressions) پیروی میکنند. الگوهای رشتهای که معنای عبارت باقاعده را نشان میدهند، ابتدا کامپایل میشوند و سپس برای تطبیق دادن همه یا بخشی از یک رشته به کار میروند.
در Elixir، رایجترین راه ساختن عبارت باقاعده استفاده از سیجیل ~r است. سیجیلها «شکر نحوی» هستند، یعنی میانبرهایی برای کارهای رایج در Elixir. برای تطبیق یک «رشتهی تحتاللفظی»، میتوانیم خودِ رشته را بهعنوان الگویی که بعد از سیجیل میآید به کار ببریم.
~r/test/
عملگر =~/2 برای انجام یک تطبیق رجکس روی یک رشته و برگرداندن نتیجهی boolean به کار میرود.
"this is a test" =~ ~r/test/
# => true
دو نکته دربارهی استفاده از سیجیلها:
/ از جداکنندههای مختلفی استفاده کنید\) گریز بدهیدتطبیق دادن یک بازه از نویسهها با براکتهای مربعی [] یک «کلاس نویسه» را تعریف میکند. این هر یک نویسه را با نویسههای موجود در کلاس مطابقت میدهد. همچنین میتوانید بازهای از نویسهها مانند a-z را مشخص کنید، به شرطی که ابتدا و انتهای آن بازهای پیوسته از نقطههای کد را نشان دهند.
regex = ~r/[a-z][ADKZ][0-9][!?]/
"jZ5!" =~ regex
# => true
"jB5?" =~ regex
# => false
«کلاسهای نویسهی کوتاهنویسیشده» الگو را مختصرتر میکنند. برای مثال:
\d کوتاهشدهی [0-9] (هر رقم)\w کوتاهشدهی [A-Za-z0-9_] (هر نویسهی «واژه»)\s کوتاهشدهی [ \t\r\n\f] (هر نویسهی فاصله)وقتی یک «کلاس نویسهی کوتاهنویسیشده» بیرون از سیجیل به کار میرود، باید گریز داده شود: "\\d"
«جانشینی» از | بهعنوان نویسهای ویژه استفاده میکند تا نشان دهد یکی یا دیگری مطابقت داده میشود.
regex = ~r/cat|bat/
"bat" =~ regex
# => true
"cat" =~ regex
# => true
«کمیتسنجها» امکان الگویی تکراری در عبارت باقاعده را فراهم میکنند. آنها روی گروهی که پیش از کمیتسنج میآید اثر میگذارند.
{N, M} که N کمترین تعداد تکرار است و M بیشترین{N,} تطبیق N تکرار یا بیشتر
{0,} را میتوان بهصورت * هم نوشت: تطبیق صفر تکرار یا بیشتر{1,} را میتوان بهصورت + هم نوشت: تطبیق یک تکرار یا بیشتر{,N} تطبیق تا N تکرارپرانتزهای گرد () برای نشان دادن «گروه»ها و «ثبت»ها به کار میروند. گاهی هم گروه ثبت میشود تا برای استفاده برگردانده شود. در Elixir، اینها میتوانند نامدار یا بینام باشند. ثبتها با افزودن ?<name> بعد از پرانتز باز نامگذاری میشوند. گروهها مانند یک واحد یکپارچه عمل میکنند، مثلاً وقتی بعد از آنها کمیتسنج میآید.
regex = ~r/(h)at/
Regex.replace(regex, "hat", "\\1op")
# => "hop"
regex = ~r/(?<letter_b>b)/
Regex.scan(regex, "blueberry", capture: :all_names)
# => [["b"], ["b"]]
«لنگرها» برای گره زدن عبارت باقاعده به ابتدا یا انتهای رشتهای که باید مطابقت داده شود به کار میروند:
^ لنگر ابتدای رشته است$ لنگر انتهای رشته استچون ~r میانبری برای "pattern" |> Regex.escape() |> Regex.compile!() است، میتوانید الگوی عبارت باقاعده را بهصورت پویا با «درونگذاری» رشته هم بسازید:
anchor = "$"
regex = ~r/end of the line#{anchor}/
"end of the line?" =~ regex
# => false
"end of the line" =~ regex
# => true
مأموریت شما نوشتن سرویسی است که رویدادها را دریافت میکند. هر رویداد تاریخی دارد، اما متوجه میشوید که ۳ قالب مختلف به اندپوینت سرویس شما فرستاده میشود:
"01/01/1970""January 1, 1970""Thursday, January 1, 1970"میبینید که میان آنها شباهتهایی وجود دارد و تصمیم میگیرید چند الگوی عبارت باقاعدهی ترکیبپذیر بنویسید.
day/0، month/0 و year/0 را طوری پیادهسازی کنید که یک الگوی رشتهای برگردانند که با کامپایل شدن، اجزای عددی "01/01/1970" را تطبیق دهد (قالب dd/mm/yyyy). روز و ماه ممکن است به شکل 1 یا 01 بیایند (با صفر در سمت چپ پر شده باشند).
"31" =~ DateParser.day() |> Regex.compile!()
# => true
"12" =~ DateParser.month() |> Regex.compile!()
# => true
"1970" =~ DateParser.year() |> Regex.compile!()
# => true
day_names/0 و month_names/0 را طوری پیادهسازی کنید که یک الگوی رشتهای برگردانند که با کامپایل شدن، بهترتیب نام روز هفته و نام ماه سال را تطبیق دهد.
"Tuesday" =~ DateParser.day_names() |> Regex.compile!()
# => true
"June" =~ DateParser.month_names() |> Regex.compile!()
# => true
capture_day/0، capture_month/0، capture_year/0، capture_day_name/0 و capture_month_name/0 را طوری پیادهسازی کنید که یک الگوی رشتهای برگردانند که اجزای مربوطه را با این نامها ضبط کند: "day"، "month"، "year"، "day_name"، "month_name"
DateParser.capture_month_name()
|> Regex.compile!()
|> Regex.named_captures("December")
# => %{"month_name" => "December"}
capture_numeric_date/0، capture_month_name_date() و capture_day_month_name_date/0 را طوری پیادهسازی کنید که یک الگوی رشتهای برگردانند که اجزای بخش ۳ را با قالب تاریخ مربوطه ضبط کند:
"01/01/1970"
"January 1, 1970"
"Thursday, January 1, 1970"
DateParser.capture_numeric_date()
|> Regex.compile!()
|> Regex.named_captures("01/01/1970")
# => %{"day" => "01", "month" => "01", "year" => "1970"}
match_numeric_date/0، match_month_name_date/0 و match_day_month_name_date/0 را طوری پیادهسازی کنید که یک عبارت باقاعدهی کامپایلشده برگردانند که فقط با تاریخ تطبیق داده شود و بتواند اجزا را هم ضبط کند.
"Thursday, January 1, 1970 was the Unix epoch." =~ DateParser.match_day_month_name_date()
# => false
"Thursday, January 1, 1970" =~ DateParser.match_day_month_name_date()
# => true
DateParser.match_day_month_name_date()
|> Regex.named_captures("Thursday, January 1, 1970")
# => %{
# "day" => "1",
# "day_name" => "Thursday",
# "month_name" => "January",
# "year" => "1970"
# }