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, "~> 1.0.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}

    # 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, _handlers) -> val <> "-converted" end
    iex> Exdn.to_elixir! "#foo \"blarg\"", [{: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! {: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 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 three parameters (tag, value, handlers)
   that handles the tagged values.

##### 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`.

##### 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.

##### 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
| --------------- | --------------------------------------------------------------------------
| 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
| set	            | mapset
| symbol	        | tagged atom `{:symbol, atom}`
| tagged elements | tagged tuple with tag and value `{:tag, Symbol, Value}`

## Author

psfblair

## License

MIT license