Packages

Ceci n'est pas une pipe - extended pipe operator

Current section

Files

Jump to
magritte lib magritte.ex
Raw

lib/magritte.ex

defmodule Magritte do
@moduledoc """
Alternative pipe operator definition.
## Usage
Just drop `use Magritte` at the top of your module and then follow with
documentation for `Margitte.|>/2` below.
"""
defmacro __using__(_) do
quote do
import Kernel, except: [|>: 2]
import unquote(__MODULE__), only: [|>: 2]
end
end
@doc """
Enchanced pipe operator.
This operator introduces the expression on the left hand as an argument to
the function call on the right hand. The position for given argument is
determined by positopn of `...` operator on the right. If that operator
is not present, then it will use first position as a default.
## Examples
```elixir
iex> [1, [2], 3] |> List.flatten()
[1, 2, 3]
```
The example above is the same as calling `List.flatten([1, [2], 3])`.
You can pick the position where the result of the left side will be
inserted:
```elixir
iex> 2 |> Integer.to_string(10, ...)
"1010"
```
The example above is the same as calling `Integer.to_string(10, 2)`.
You can also join these into longer chains:
```elixir
iex> 2 |> Integer.to_string(10, ...) |> Integer.parse
{1010, ""}
```
The operator `...` can be used only once in the pipeline, otherwise
it will return compile-time error:
```elixir
2 |> Integer.to_string(..., ...)
** (CompileError) Doubled placeholder in Integer.to_string(..., ...)
```
"""
defmacro left |> right do
[{h, _} | t] = unpipe({:|>, [], [left, right]}, __CALLER__)
fun = fn {x, pos}, acc ->
Macro.pipe(acc, x, pos)
end
:lists.foldl(fun, h, t)
end
defp unpipe(ast, caller), do: :lists.reverse(unpipe(ast, [], caller))
defp unpipe({:|>, _, [left, right]}, acc, caller) do
unpipe(right, unpipe(left, acc, caller), caller)
end
defp unpipe(ast, acc, %Macro.Env{line: line, file: file}) do
case find_pos(ast) do
{:ok, new_ast, pos} ->
[{new_ast, pos} | acc]
{:error, {:already_found, _, _}} ->
raise CompileError,
file: file,
line: line,
description: "Doubled placeholder in #{Macro.to_string(ast)}"
end
end
defguardp is_empty(a) when a == [] or not is_list(a)
defp find_pos({fun, env, args}) when not is_empty(args) do
with {:ok, found, new_args} <- locate(args, 0, nil, []),
do: {:ok, {fun, env, new_args}, found}
end
defp find_pos(ast), do: {:ok, ast, 0}
pattern = quote do: {:..., _, var!(args)}
defp locate([unquote(pattern) | rest], pos, nil, acc) when is_empty(args),
do: locate(rest, pos + 1, pos, acc)
defp locate([unquote(pattern) | _], pos, found, _acc) when is_empty(args),
do: {:error, {:already_found, found, pos}}
defp locate([arg | rest], pos, found, args),
do: locate(rest, pos + 1, found, [arg | args])
defp locate([], _, found, args),
do: {:ok, found || 0, :lists.reverse(args)}
end