Skip to content

A simple and fast parser combinator library for Elixir

Notifications You must be signed in to change notification settings

bitwalker/nimble_parsec

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NimbleParsec

NimbleParsec is a simple and fast library for parser combinators.

Combinators are built during runtime and compile into multiple clauses with binary matching. This provides the following benefits:

  • Performance: since it compiles to binary matching, it leverages many Erlang VM optimizations to generate extremely fast parser code with low memory usage

  • Composable: this library does not rely on macros for building and composing parsers, therefore they are fully composable. The only macros are defparsec/3 and defparsecp/3 which emit the compiled clauses with binary matching

  • No runtime dependency: after compile, the generated parser clauses have no runtime dependency on NimbleParsec. This opens up the possibility to compile parsers and do not impose a dependency on users of your library

  • No footprint: NimbleParsec only needs to be imported in your modules. Leaving no footprint on your modules.

Examples

defmodule MyParser do
  import NimbleParsec

  date =
    integer(4)
    |> ignore(literal("-"))
    |> integer(2)
    |> ignore(literal("-"))
    |> integer(2)

  time =
    integer(2)
    |> ignore(literal(":"))
    |> integer(2)
    |> ignore(literal(":"))
    |> integer(2)
    |> optional(literal("Z"))

  defparsec :datetime, date |> ignore(literal("T")) |> concat(time), debug: true
end

MyParser.datetime("2010-04-17T14:12:34Z")
#=> {:ok, [2010, 4, 17, 14, 12, 34, "Z"], "", 1, 21}

If you add debug: true to defparsec/3, it will print the generated clauses, which are shown below:

defp datetime__0(<<x0, x1, x2, x3, "-", x4, x5, "-", x6, x7, "T",
                   x8, x9, ":", x10, x11, ":", x12, x13, rest::binary>>,
                 acc, stack, combinator__line, combinator__column)
     when x0 >= 48 and x0 <= 57 and (x1 >= 48 and x1 <= 57) and
         (x2 >= 48 and x2 <= 57) and (x3 >= 48 and x3 <= 57) and
         (x4 >= 48 and x4 <= 57) and (x5 >= 48 and x5 <= 57) and
         (x6 >= 48 and x6 <= 57) and (x7 >= 48 and x7 <= 57) and
         (x8 >= 48 and x8 <= 57) and (x9 >= 48 and x9 <= 57) and
         (x10 >= 48 and x10 <= 57) and (x11 >= 48 and x11 <= 57) and
         (x12 >= 48 and x12 <= 57) and (x13 >= 48 and x13 <= 57) do
  datetime__1(
    rest,
    [(x13 - 48) * 1 + (x12 - 48) * 10, (x11 - 48) * 1 + (x10 - 48) * 10,
     (x9 - 48) * 1 + (x8 - 48) * 10, (x7 - 48) * 1 + (x6 - 48) * 10, (x5 - 48) * 1 + (x4 - 48) * 10,
     (x3 - 48) * 1 + (x2 - 48) * 10 + (x1 - 48) * 100 + (x0 - 48) * 1000] ++ acc,
    stack,
    combinator__line,
    combinator__column + 19
  )
end

defp datetime__0(rest, acc, stack, line, column) do
  {:error, "...", rest, line, column}
end

defp datetime__1(<<"Z", rest::binary>>, acc, stack, combinator__line, combinator__column) do
  datetime__2(rest, ["Z"] ++ acc, stack, combinator__line, combinator__column + 1)
end

defp datetime__1(rest, acc, stack, line, column) do
  datetime__2(rest, acc, stack, line, column)
end

defp datetime__2(rest, acc, _stack, line, column) do
  {:ok, acc, rest, line, column}
end

As you can see, it generates highly inlined code, comparable to hand-written parsers. This gives NimbleParsec an order of magnitude performance gains compared to other parser combinators. Further performance can be gained by giving the inline: true option to defparsec/3.

Instalation

Add nimble_parsec to your list of dependencies in mix.exs:

def deps do
  [{:nimble_parsec, "~> 0.1"}]
end

License

Copyright 2018 Plataformatec

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

About

A simple and fast parser combinator library for Elixir

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Elixir 100.0%