Current section
Files
Jump to
Current section
Files
lib/magritte.ex
defmodule Magritte do
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
defp find_pos({fun, env, 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}
defguardp is_empty(a) when a == [] or not is_list(a)
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