Commit b680684f3f9b2105b9c1f276adc05e5fe6a26022

Thomas de Grivel 2022-02-17T11:36:40

Purely functional Elixir

diff --git a/lib/pure.ex b/lib/pure.ex
index 99c4a90..6ded7e1 100644
--- a/lib/pure.ex
+++ b/lib/pure.ex
@@ -50,6 +50,19 @@ defmodule Pure do
   end
 
   @doc """
+  Checks if string is a match expression.
+
+  ## Examples
+
+      iex> Pure.string_is_match?("{a, b} = File.read(:stdin)", __ENV__)
+      true
+  """
+  def string_is_match?(str, env) when is_binary(str) do
+    {:ok, quoted} = Code.string_to_quoted(str)
+    is_match?(quoted, env)
+  end    
+
+  @doc """
   Checks if quoted expression is a match expression.
 
   ## Examples
@@ -62,20 +75,28 @@ defmodule Pure do
     {erl, _, _, _env} = :elixir.quoted_to_erl(quoted, env)
     IO.inspect(erl, limit: :infinity)
     case erl do
-      {:match, _, _, _} -> true
+      {:match, _, a, b} ->
+        a_is_pattern? = :pure.is_pattern_expr(a)
+        b_is_guard? = :erl_lint.is_guard_test(b)
+        IO.inspect([a: a, is_pattern?: a_is_pattern?])
+        IO.inspect([b: b, is_guard?: b_is_guard?])
+        a_is_pattern? && b_is_guard?
       _ -> false
     end
   end    
 
   defmacro eval(str) when is_binary(str) do
-    q = Code.string_to_quoted(str)
-    quote do eval(unquote(q)) end
+    {:ok, q} = Code.string_to_quoted(str)
+    quote do
+      require Pure
+      Pure.eval(unquote(q))
+    end
   end
   defmacro eval(q) do
-    if is_guard?(q, __ENV__) do
+    if is_guard?(q, __ENV__) || is_match?(q, __ENV__) do
       q
     else
-      raise ArgumentError, "not a valid guard expression: #{Macro.to_string(q)}"
+      raise ArgumentError, "not a pure expression: #{Macro.to_string(q)}"
     end
   end
 end
diff --git a/src/pure.erl b/src/pure.erl
new file mode 100644
index 0000000..b4f3399
--- /dev/null
+++ b/src/pure.erl
@@ -0,0 +1,38 @@
+-module(pure).
+
+-export([is_pattern_expr/1]).
+
+-import(lists, [all/2]).
+
+is_pattern_expr(Expr) ->
+    case is_pattern_expr_1(Expr) of
+	false -> false;
+	true ->
+	    %% Expression is syntactically correct - make sure that it
+	    %% also can be evaluated.
+	    case erl_eval:partial_eval(Expr) of
+		{integer,_,_} -> true;
+		{char,_,_} -> true;
+		{float,_,_} -> true;
+		{atom,_,_} -> true;
+		{var,_,_} -> true;
+                {tuple,_,_} -> true;
+		X -> io:write(X), false
+	    end
+    end.
+
+is_pattern_expr_1({char,_Anno,_C}) -> true;
+is_pattern_expr_1({integer,_Anno,_I}) -> true;
+is_pattern_expr_1({float,_Anno,_F}) -> true;
+is_pattern_expr_1({atom,_Anno,_A}) -> true;
+is_pattern_expr_1({tuple,_Anno,Es}) ->
+    all(fun is_pattern_expr/1, Es);
+is_pattern_expr_1({var,_Anno,_V}) -> true;
+is_pattern_expr_1({nil,_Anno}) -> true;
+is_pattern_expr_1({cons,_Anno,H,T}) ->
+    is_pattern_expr_1(H) andalso is_pattern_expr_1(T);
+is_pattern_expr_1({op,_Anno,Op,A}) ->
+    erl_internal:arith_op(Op, 1) andalso is_pattern_expr_1(A);
+is_pattern_expr_1({op,_Anno,Op,A1,A2}) ->
+    erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr/1, [A1,A2]);
+is_pattern_expr_1(_Other) -> false.