Current section
Files
Jump to
Current section
Files
lib/niffler.ex
defmodule Niffler do
@on_load :init
@moduledoc """
Documentation for `Niffler`.
"""
@doc false
defmacro __using__(_opts) do
quote do
import Niffler
@niffler_module __MODULE__
end
end
defmacro defnif(name, inputs, outputs, do: source) do
quote do
def unquote(name)(args) do
key = {@niffler_module, unquote(name)}
:persistent_term.get(key, nil)
|> case do
nil ->
prog = Niffler.compile!(unquote(source), unquote(inputs), unquote(outputs))
:persistent_term.put(key, prog)
prog
prog ->
prog
end
|> Niffler.run(args)
end
end
end
@doc false
def gen!(key, source, inputs, outputs) do
:persistent_term.put(key, Niffler.compile!(source, inputs, outputs))
end
@doc false
def init do
:ok =
case :code.priv_dir(:niffler) do
{:error, :bad_name} ->
if File.dir?(Path.join("..", "priv")) do
Path.join("..", "priv")
else
"priv"
end
path ->
path
end
|> Path.join("niffler.nif")
|> String.to_charlist()
|> :erlang.load_nif(0)
end
@doc """
Compile takes a string as input and compiles it into a nif program. Returning the program
reference.any()
## Examples
iex> {:ok, prog} = Niffler.compile("ret = a * b;", [a: :int, b: :int], [ret: :int])
iex> Niffler.run(prog, [3, 4])
{:ok, [12]}
iex> code = "ret = 0; for (int i = 0; i < str.size; i++) if (str.data[i] == 0) ret++;"
iex> {:ok, prog} = Niffler.compile(code, [str: :binary], [ret: :int])
iex> Niffler.run(prog, [<<0,1,1,0,1,5,0>>])
{:ok, [3]}
"""
def compile(code, inputs, outputs)
when is_binary(code) and is_list(inputs) and is_list(outputs) do
type_defs =
[
Enum.with_index(inputs)
|> Enum.map(fn {{name, type}, idx} ->
"#define #{name} (input[#{idx}].#{value_name(type)})"
end),
Enum.with_index(outputs)
|> Enum.map(fn {{name, type}, idx} ->
"#define #{name} (output[#{idx}].#{value_name(type)})"
end)
]
|> Enum.concat()
|> Enum.join("\n ")
run =
if String.contains?(code, "DO_RUN {") do
String.replace(code, "DO_RUN {", """
void run(Param *input, Param *output) {
#{type_defs}
""")
else
"""
void run(Param *input, Param *output) {
#{type_defs}
#{code}
}
"""
end
code = """
#{header()}
#{run}
"""
case nif_compile(code <> <<0>>, inputs, outputs) do
{:error, message} ->
message =
if message == "compilation error" do
lines =
String.split(code, "\n")
|> Enum.with_index(1)
|> Enum.map(fn {line, num} -> String.pad_leading("#{num}: ", 4) <> line end)
|> Enum.join("\n")
IO.puts(lines)
message <> " in '#{code}'"
else
message
end
{:error, message}
other ->
other
end
end
def compile!(code, inputs, outputs) do
{:ok, prog} = compile(code, inputs, outputs)
prog
end
defp nif_compile(_code, _inputs, _outputs) do
:erlang.nif_error(:nif_library_not_loaded)
end
@doc """
Executes the given Niffler program and return any output values
## Examples
iex> {:ok, prog} = Niffler.compile("ret = a << 2;", [a: :int], [ret: :int])
iex> Niffler.run(prog, [5])
{:ok, [20]}
"""
def run(prog, args) do
nif_run(prog, args)
end
def run!(prog, args) do
{:ok, ret} = run(prog, args)
ret
end
defp nif_run(_state, _args) do
:erlang.nif_error(:nif_library_not_loaded)
end
defp value_name(:int), do: "integer64"
defp value_name(:int64), do: "integer64"
defp value_name(:uint64), do: "uinteger64"
defp value_name(:double), do: "doubleval"
defp value_name(:binary), do: "binary"
defp header() do
"""
typedef signed char int8_t;
typedef unsigned char uint8_t;
typedef short int16_t;
typedef unsigned short uint16_t;
typedef int int32_t;
typedef unsigned uint32_t;
typedef long long int64_t;
typedef unsigned long long uint64_t;
typedef struct {
uint64_t size;
unsigned char* data;
} Binary;
typedef union {
Binary binary;
int64_t integer64;
uint64_t uinteger64;
double doubleval;
} Param;
"""
|> String.trim()
end
end