README.md

# Exdn - an edn parser for the Elixir platform

Exdn is a two-way translator between Elixir data structures and data 
following the [edn specification](https://github.com/edn-format/edn);
it wraps the [erldn edn parser](https://github.com/marianoguerra/erldn) 
for Erlang, with some changes in the data formats (see below).

 * [Installation](#installation)
 * [Usage](#usage)
 * [API](#api)
 * [Type Mappings](#type-mappings)
 * [Author](#author)
 * [License](#license)

## Installation

Once [available in Hex](https://hex.pm/docs/publish), the package can be installed 
by adding exdn to your list of dependencies in `mix.exs`:

    def deps do
      [{:exdn, "~> 2.2.0"}]
    end

## Usage

    iex> Exdn.to_elixir! "41.2"
      41.2

    iex> Exdn.to_elixir! ":foo"
      :foo

    iex> Exdn.to_elixir! "true"
      true

    iex> Exdn.to_elixir! "nil"
      nil

    iex> Exdn.to_elixir! "\"asd\""
      "asd"

    # Char
    iex> Exdn.to_elixir! "\\a"
      "a"
    
    # Symbol
    iex> Exdn.to_elixir! "foo"
      {:symbol, :foo}

    # edn vectors become Elixir lists:
    iex> Exdn.to_elixir! "[1 :foo]"
      [1, :foo]

    # edn lists are always tagged. Since Datomic is a principal use of edn, and since lists are 
    # used in Datomic primarily for executable expressions rather than as data structures, we 
    # use Elixir lists to represent vectors and keep edn lists specially tagged:    
    iex> Exdn.to_elixir! "(1, :foo)"
      {:list, [1, :foo]}

    # edn sets become Elixir sets:
    iex> Exdn.to_elixir! "\#{1 \\a 1}"
      #MapSet<[1, "a"]>

    # Maps become Elixir maps:
    iex> Exdn.to_elixir! "{1 :foo, 2 :bar}"
      %{1 => :foo, 2 => :bar}
      
    # You can also transform maps to Elixir structs by providing your own converter in the second argument:
    iex> defmodule FooStruct do
    ...>    defstruct foo: "default"
    ...> end
    iex> converter = fn map ->
    ...>    case map do
    ...>       %{:foo => _} -> struct(FooStruct, map)
    ...>       anything_else -> anything_else
    ...>     end
    ...>   end
    iex>  Exdn.to_elixir! "{:foo 1, :bar 2}", converter
       %FooStruct{foo: 1}      

    # Tagged expressions are converted. Standard converters for #inst and #uuid are included:
    iex> Exdn.to_elixir! "#inst \"1985-04-12T23:20:50.52Z\"" 
      %Calendar.DateTime{abbr: "UTC", day: 12, hour: 23, min: 20, month: 4, sec: 50,
        std_off: 0, timezone: "Etc/UTC", usec: 520000, utc_off: 0, year: 1985}

    iex> Exdn.to_elixir! "#uuid \"f81d4fae-7dec-11d0-a765-00a0c91e6bf6\"" 
      "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"

    # You can provide your own handlers for tagged expressions:
    iex> handler = fn(_tag, val, _converter, _handlers) -> val <> "-converted" end
    iex> identity = &(&1)
    iex> Exdn.to_elixir! "#foo \"blarg\"", identity, [{:foo, handler}] 
      "blarg-converted"

    # There is a safe version that doesn't raise exceptions:
    iex> Exdn.to_elixir "{1 :foo, 2 :bar}"
      {:ok, %{1 => :foo, 2 => :bar}}

    iex> Exdn.to_elixir "{:foo, \\a, \\b #foo \"blarg\" }"
      {:error, %RuntimeError{:message => "Handler not found for tag foo with tagged expression blarg"}}

    # There is also an "intermediate" representation that can be converted back to edn. The 
    # difference is that chars and tagged expressions are converted to tagged tuples:    
    iex> Exdn.to_reversible( "\\a" )
      {:char, ?a}
    
    iex> Exdn.to_reversible "#inst \"1985-04-12T23:20:50.52Z\""
      {:tag, :inst, "1985-04-12T23:20:50.52Z"}

    # An unknown tag raises no error when using the reversible conversion:
    iex> Exdn.to_reversible "#foo \"blarg\""
      {:tag, :foo, "blarg"}

    # The intermediate representation can be converted back to edn:
    iex> Exdn.from_elixir! 41.2
      "41.2"

    iex> Exdn.from_elixir! :foo
      ":foo"

    iex> Exdn.from_elixir! true
      "true"

    iex> Exdn.from_elixir! nil
      "nil"

    iex> Exdn.from_elixir! "asd"
      "\"asd\""

    iex> Exdn.from_elixir! {:char, ?a}
      "\\a"

    iex> Exdn.from_elixir! {:symbol, :foo}
      "foo"

    iex> Exdn.from_elixir! [1, :foo]
      "[1 :foo]"

    iex> Exdn.from_elixir! {:list, [1, :foo]}
      "(1 :foo)"

    iex> Exdn.from_elixir! MapSet.new([1, :foo])
      "\#{1 :foo}"

    iex> Exdn.from_elixir! %{1 => :foo, 2 => :bar}
      "{1 :foo 2 :bar}"

    iex> Exdn.from_elixir! %SomeStruct{foo: 1, bar: 2}
      "{:foo 1 :bar 2}"

    iex> Exdn.from_elixir! {:tag, :inst, "1985-04-12T23:20:50.52Z"}
      "#inst \"1985-04-12T23:20:50.52Z\""

    # There is a safe version for converting back to edn that doesn't raise exceptions:
    iex> Exdn.from_elixir %{:foo => {:char, ?a}, {:char, ?b} => {:tag, :inst, "1985-04-12T23:20:50.52Z"} }
      {:ok, "{:foo \\a \\b #inst \"1985-04-12T23:20:50.52Z\"}" }
    
    # There are also converters you can use if you want to handle chars, lists, or tags on an ad-hoc basis:
    iex> Exdn.tagged_list_to_list {:list, [:foo]}
      [:foo]
    
    iex> Exdn.tagged_char_to_string {:char, ?a}
      "a"
    
    iex> handler = fn(_tag, val, _handlers) -> val <> "-converted" end
    iex> Exdn.evaluate_tagged_expr {:tag, :foo, "blarg"}, [{:foo, handler}])
      "blarg-converted"

## API

##### to_elixir!/1

   parses an edn string into an Elixir data structure; this is not a reversible
   conversion as chars are converted to strings, and tagged expressions are 
   interpreted. This function can throw exceptions; for example, if a tagged
   expression cannot be interpreted.

##### to_elixir!/2

   the second argument allows you to supply your own converter function for any of the
   incoming data; your function will be applied recursively to every value in the edn 
   parse tree. Generally you will want to use it to convert maps to structs, but
   you can use it on any incoming data value, including tagged values. (The
   tagged value is first passed to the converter and then, if it is still tagged,
   to the tagged value handlers (see the three-argument version of `to_elixir!` 
   below for more on handlers). The conversion function should be a function of 
   one parameter; generally you will want to pattern-match on incoming values 
   with a default clause that returns the untransformed value.

##### to_elixir!/3

   the third argument allows you to supply your own handlers for the interpretation
   of tagged expressions. These should be in the form of a keyword list.
   The first element of each pair should be a keyword corresponding to the tag,
   and the second element a function of four parameters (tag, value, converter, handlers)
   that handles the tagged values. Generally you will want to operate only on the
   value, but you can also use the tag, the converter passed to `to_elixir!` as
   its second parameter, or the handlers passed to `to_elixir!` as the third parameter.

##### to_elixir/1

   also parses an edn string into an Elixir data structure, but does not throw
   exceptions. The parse result is returned as the second element of a pair 
   whose first element is `:ok` -- if there is an error the first element will
   be `:error` and the second the error that was raised.

##### to_elixir/2

   safe version of `to_elixir!/2`.

##### to_elixir/3

   safe version of `to_elixir!/3`.

##### from_elixir!/1

   converts an Elixir data structure in the "reversible" format (see below) into 
   an edn string. Will raise exceptions if the data structure cannot be converted.
   Structs will be converted to edn maps.

##### from_elixir/1

   safe version of `from_elixir!/1` -- the edn string is returned as the second 
   element of a pair whose first element is `:ok` -- if there is an error the first 
   element will be `:error` and the second the error that was raised.

##### to_reversible/1    

   parses an edn string into an Elixir data structure, but in a reversible way --
   chars and tagged expressions are represented using tuples whose first element
   is `:char` or `:tag`, respectively.


## Type Mappings

| edn	            | Elixir generated by `to_elixir` functions when no custom converter is provided
| --------------- | --------------------------------------------------------------------------
| integer	        | integer
| float	          | float
| boolean	        | boolean
| nil	            | nil (atom)
| char            | string
| string	        | string
| list	          | tagged list `{:list, [...]}`
| vector	        | list
| map	            | map
| set	            | mapset
| symbol	        | tagged atom `{:symbol, atom}`
| tagged elements	| call registered handler for that tag, fail if not found


### Reversible Mappings

| edn             | Elixir generated by `to_reversible` or accepted by `from_elixir` functions
| --------------- | --------------------------------------------------------------------------
| integer	        | integer
| float	          | float
| boolean	        | boolean
| nil	            | nil (atom)
| char            | tagged integer `{:char, <integer>}`
| string	        | string
| list	          | tagged list `{:list, [...]}`
| vector	        | list
| map	            | map
| struct          | map
| set	            | mapset
| symbol	        | tagged atom `{:symbol, atom}`
| tagged elements | tagged tuple with tag and value `{:tag, Symbol, Value}`

## Author

psfblair

## License

MIT license