Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ iex> Complex.new(0, 1)
%Complex{im: 1.0, re: 0.0}
```

### Sigil

`import Complex, only: :sigils` gives you the `~COMPLEX` and `~POLAR` sigils.
The two numbers are separated by whitespace; `~COMPLEX` mirrors Common
Lisp's `#c(re im)` reader syntax. They are meant for literals; for
variables use `Complex.new/2` and `Complex.from_polar/2`:

```elixir
iex> ~COMPLEX(3 4)
%Complex{im: 4, re: 3}

iex> ~POLAR(2 1.57)
Complex.from_polar(2, 1.57)
```

## License

Copyright 2015 Thomas Krauss
Expand Down
63 changes: 63 additions & 0 deletions lib/complex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,69 @@ defmodule Complex do
end
end

@doc ~S"""
Handles the sigil `~COMPLEX` for complex numbers in cartesian form.

The real and imaginary parts are separated by whitespace, mirroring
Common Lisp's `#c(re im)` reader syntax. Uppercase sigils do not
interpolate, so for variables use `new/2`.

Use `import Complex, only: :sigils` to make it available.

### See also

`new/2`, `sigil_POLAR/2`

### Examples

import Complex, only: :sigils

~COMPLEX(3 4)
#=> %Complex{re: 3, im: 4}
"""
@spec sigil_COMPLEX(String.t(), []) :: t
def sigil_COMPLEX(string, []) do
[re, im] = sigil_numbers(string)
new(re, im)
end

@doc ~S"""
Handles the sigil `~POLAR` for complex numbers in polar form.

The magnitude and the angle (in radians) are separated by whitespace.
Uppercase sigils do not interpolate, so for variables use `from_polar/2`.

Use `import Complex, only: :sigils` to make it available.

### See also

`from_polar/2`, `sigil_COMPLEX/2`

### Examples

import Complex, only: :sigils

~POLAR(2 1.57)
#=> Complex.from_polar(2, 1.57)
"""
@spec sigil_POLAR(String.t(), []) :: t
def sigil_POLAR(string, []) do
[r, phi] = sigil_numbers(string)
from_polar(r, phi)
end

defp sigil_numbers(string), do: string |> String.split() |> Enum.map(&sigil_number/1)

defp sigil_number(str) do
with :error <- exact_parse(Integer.parse(str)),
:error <- exact_parse(Float.parse(str)) do
raise ArgumentError, "invalid number in complex sigil: #{inspect(str)}"
end
end

defp exact_parse({n, ""}), do: n
defp exact_parse(_), do: :error

@doc """
Returns the phase angle of the supplied complex, in radians.

Expand Down
43 changes: 43 additions & 0 deletions test/complex/sigils_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
defmodule Complex.SigilsTest do
use ExUnit.Case, async: true

import Complex, only: :sigils

test "~COMPLEX builds a complex number from integers" do
assert ~COMPLEX(3 4) == Complex.new(3, 4)
end

test "~COMPLEX keeps integers and floats as written" do
assert ~COMPLEX(3 -4.5) === Complex.new(3, -4.5)
end

test "extra whitespace is ignored" do
assert ~COMPLEX( 3 4 ) == Complex.new(3, 4)
end

test "~POLAR builds a complex number from magnitude and angle" do
assert ~POLAR(2 1.57) == Complex.from_polar(2, 1.57)
end

test "it raises on an invalid number" do
assert_raise ArgumentError, ~r/invalid number/, fn ->
Complex.sigil_COMPLEX("3 x", [])
end

assert_raise ArgumentError, ~r/invalid number/, fn ->
Complex.sigil_POLAR("3 x", [])
end
end

test "it raises when there are not exactly two numbers" do
assert_raise MatchError, fn -> Complex.sigil_COMPLEX("3", []) end
assert_raise MatchError, fn -> Complex.sigil_POLAR("1 2 3", []) end
end

test "it raises on modifiers" do
# apply/3 keeps the compiler's type checker from flagging the call,
# which can never match the `[]` modifiers clause
assert_raise FunctionClauseError, fn -> apply(Complex, :sigil_COMPLEX, ["3 4", ~c"x"]) end
assert_raise FunctionClauseError, fn -> apply(Complex, :sigil_POLAR, ["3 4", ~c"p"]) end
end
end