Last active
September 21, 2022 03:06
-
-
Save henrik/d482d41288d732f97f2d to your computer and use it in GitHub Desktop.
Proof-of-concept of an Elixir construct like `case` but that matches strings against regexps. #elixirlang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule RegexCase do | |
defmacro regex_case(string, do: lines) do | |
new_lines = Enum.map lines, fn ({:->, context, [[regex], result]}) -> | |
condition = quote do: String.match?(unquote(string), unquote(regex)) | |
{:->, context, [[condition], result]} | |
end | |
# Base case if nothing matches; "cond" complains otherwise. | |
base_case = quote do: (true -> nil) | |
new_lines = new_lines ++ base_case | |
quote do | |
cond do | |
unquote(new_lines) | |
end | |
end | |
end | |
end | |
defmodule Run do | |
import RegexCase | |
def run do | |
regex_case "hello" do | |
~r/x/ -> IO.puts("matches x") | |
~r/e/ -> IO.puts("matches e") | |
~r/y/ -> IO.puts("matches y") | |
end | |
end | |
end | |
Run.run |
how do you have an escape clause:
let's say i want to do something if the item matches and simply return the item if it doesn't:
regex_case item do
~r/x/ -> do_y(item)
true -> item
end
Hi @Neophen! One way would be to allow anelse
block:
defmodule RegexCase do
defmacro regex_case(string, opts) do
lines = Keyword.fetch!(opts, :do)
else_case = Keyword.get(opts, :else, nil)
new_lines = Enum.map lines, fn ({:->, context, [[regex], result]}) ->
condition = quote do: String.match?(unquote(string), unquote(regex))
{:->, context, [[condition], result]}
end
base_case = quote do: (true -> unquote(else_case))
new_lines = new_lines ++ base_case
quote do
cond do
unquote(new_lines)
end
end
end
end
defmodule Run do
import RegexCase
def run do
regex_case "hello" do
~r/x/ -> IO.puts("matches x")
else
IO.puts("no match")
end
end
end
Run.run
Another would be to pass through true -> _whatever
clauses as-is:
defmodule RegexCase do
defmacro regex_case(string, do: lines) do
new_lines = Enum.map lines, fn
({:->, _context, [[true], _result]} = true_case) -> true_case
({:->, context, [[regex], result]}) ->
condition = quote do: String.match?(unquote(string), unquote(regex))
{:->, context, [[condition], result]}
end
# Base case, for when nothing matches and we didn't provide our own base case; "cond" complains otherwise.
base_case = quote do: (true -> nil)
new_lines = new_lines ++ base_case
quote do
cond do
unquote(new_lines)
end
end
end
end
defmodule Run do
import RegexCase
def run do
regex_case "hello" do
~r/x/ -> IO.puts("matches x")
true -> IO.puts("no match")
end
end
end
Run.run
Seems it's fine to have the macro always define its own true -> nil
clause as a fallback, even if you define your own (that will match instead, because yours is earlier).
A third idea is to just keep the current code and use an empty regex as the base case, since it will match any string:
regex_case "hello" do
~r/x/ -> IO.puts("matches x")
~r// -> IO.puts("no match")
end
This one won't work if you're expecting non-string input.
Awesome the second example with provide the true is a nicer syntax! thank you Henrik
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
License: Public domain. No attribution needed.