Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hansihe committed Mar 3, 2017
0 parents commit 5379eb3
Show file tree
Hide file tree
Showing 23 changed files with 1,827 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# The directory Mix will write compiled artifacts to.
/_build

# If you run "mix test --cover", coverage assets end up here.
/cover

# The directory Mix downloads your dependencies sources to.
/deps

# Where 3rd-party dependencies like ExDoc output generated docs.
/doc

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

/priv/native/*
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Jinx

**TODO: Add description**

## Installation

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

```elixir
def deps do
[{:jinx, "~> 0.1.0"}]
end
```

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
be found at [https://hexdocs.pm/jinx](https://hexdocs.pm/jinx).

30 changes: 30 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure for your application as:
#
# config :jinx, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:jinx, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
18 changes: 18 additions & 0 deletions lib/juicy.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Juicy do
@moduledoc """
Documentation for Juicy.
"""

def parse(binary) do
Juicy.Basic.parse(binary)
end

def parse_stream(stream, spec) do
Juicy.Stream.stream(stream, spec)
end

def validate_spec(spec) do
Juicy.Native.validate_spec(spec)
end

end
14 changes: 14 additions & 0 deletions lib/juicy/basic.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Juicy.Basic do

def parse(binary) do
handle_parse_return(binary, Juicy.Native.parse_init(binary))
end

defp handle_parse_return(binary, {:iter, stack, res}) do
handle_parse_return(binary, Juicy.Native.parse_iter(binary, stack, res))
end
defp handle_parse_return(_, ret) do
ret
end

end
13 changes: 13 additions & 0 deletions lib/juicy/native.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
defmodule Juicy.Native do
use Rustler, otp_app: :juicy, crate: "juicy_native"

def parse_init(_), do: err()
def parse_iter(_, _, _), do: err()

def stream_parse_init(_), do: err()
def stream_parse_iter(_, _), do: err()

def validate_spec(_), do: err()

defp err, do: throw NifNotLoadedError
end
116 changes: 116 additions & 0 deletions lib/juicy/stream.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
defmodule Juicy.Stream do

defstruct reader: nil, spec: nil, binaries: nil, parser: nil, output_queue: [], state: {:read_input, :parsing_not_done, nil}

def stream(input, spec) do
reader = input
|> Stream.transform(0, fn(elem, pos) -> {[{pos, elem}], pos+byte_size(elem)} end)
|> stream_take_init

%__MODULE__{
reader: reader,
spec: spec,
}
end

def stream_take_init(stream) do
reduce_fun = fn(elem, nil) -> {:suspend, elem} end
{:suspended, nil, next_fun} = Enumerable.reduce(stream, {:suspend, nil}, reduce_fun)
next_fun
end
def stream_take_next(next_fun) do
next_fun.({:cont, nil})
end
def stream_take_halt(next_fun) do
next_fun.({:halt, nil})
end

end

defimpl Enumerable, for: Juicy.Stream do

def count(_stream) do
{:error, __MODULE__}
end
def member?(_stream) do
{:error, __MODULE__}
end

def reduce(js = %Juicy.Stream{}, acc, fun) do
{:ok, parser} = Juicy.Native.stream_parse_init(js.spec)
js = %Juicy.Stream{ js |
parser: parser,
binaries: [],
}
do_reduce(js, acc, fun)
end

defp do_reduce(js, {:halt, acc}, fun) do
Juicy.Stream.stream_take_halt(js.reader)
{:halted, acc}
end
defp do_reduce(js, {:suspend, acc}, fun) do
{:suspended, acc, &do_reduce(js, &1, fun)}
end

defp do_reduce(js, {:cont, acc}, fun) do
{transition, js} =
case js.state do

{:read_input, :parsing_not_done, _} ->
case Juicy.Stream.stream_take_next(js.reader) do
{:suspended, binary, reader} ->
js = %{js |
binaries: [binary | js.binaries],
reader: reader,
state: {:parse, :parsing_not_done, nil},
}
{:loop, js}
{:halted, _} ->
js = %{js |
state: {:emit_items, :parsing_done, nil},
output_queue: [{:error, :early_eoi}],
}
{:loop, js}
end

{:parse, :parsing_not_done, _} ->
{status, yields, binaries, state} = Juicy.Native.stream_parse_iter(js.binaries, js.parser)
js = %{js | output_queue: yields, parser: state, binaries: binaries}
case status do
:finished -> {:loop, %{js | state: {:emit_items, :parsing_done, nil}}}
:iter -> {:loop, %{js | state: {:emit_items, :parsing_not_done, nil}}}
:await_input -> {:loop, %{js | state: {:emit_items, :parsing_not_done, :await_input}}}
end

{:emit_items, :parsing_done, _} ->
case js.output_queue do
[] -> {:done, js}
_ -> {:emit_output_item, js}
end

{:emit_items, :parsing_not_done, :await_input} ->
case js.output_queue do
[] -> {:loop, %{js | state: {:read_input, :parsing_not_done, nil}}}
_ -> {:emit_output_item, js}
end

{:emit_items, :parsing_not_done, _} ->
case js.output_queue do
[] -> {:loop, %{js | state: {:parse, :parsing_not_done, nil}}}
_ -> {:emit_output_item, js}
end

end

case transition do
:loop -> do_reduce(js, {:cont, acc}, fun)
:emit_output_item ->
[head | tail] = js.output_queue
js = %{js | output_queue: tail}
do_reduce(js, fun.(head, acc), fun)
:done -> {:done, acc}
end
end

end
45 changes: 45 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule Juicy.Mixfile do
use Mix.Project

def project do
[app: :juicy,
version: "0.1.0",
elixir: "~> 1.4",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
compilers: [:rustler] ++ Mix.compilers,
rustler_crates: rustler_crates(),
deps: deps()]
end

# Configuration for the OTP application
#
# Type "mix help compile.app" for more information
def application do
# Specify extra applications you'll use from Erlang/Elixir
[extra_applications: [:logger]]
end

# Dependencies can be Hex packages:
#
# {:my_dep, "~> 0.3.0"}
#
# Or git/path repositories:
#
# {:my_dep, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
#
# Type "mix help deps" for more examples and options
defp deps do
[{:rustler, "~> 0.8.0"}]
end

defp rustler_crates do
[
juicy_native: [
path: "native/juicy_native",
cargo: :system,
mode: :release,
]
]
end
end
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%{"rustler": {:hex, :rustler, "0.8.0", "337e2b39c80dafb4a5d65d7791a9a97b3d566941bdb5c718d33488025e0299b0", [:mix], []}}
Loading

0 comments on commit 5379eb3

Please sign in to comment.