Skip to content

Commit

Permalink
Wrap Engine pt1 (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
fabriziosestito authored Feb 8, 2023
1 parent 5b1373e commit c3aa26f
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 5 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ rhai_rustler-*.tar
/priv/native/*

checksum-Elixir.Rhai.Native.exs

.elixir-ls
1 change: 1 addition & 0 deletions lib/rhai.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ defmodule Rhai do
{:error, {:variable_not_found, "Variable not found: b (line 1, position 5)"}}
"""
@doc since: "0.1.0"
@deprecated "Use Rhai.Engine instead"
@spec eval(String.t() | Rhai.PrecompiledExpression.t(), map()) ::
{:ok, rhai_any()} | {:error, {rhai_error(), String.t()}}
def eval(expression, context \\ %{}) do
Expand Down
59 changes: 59 additions & 0 deletions lib/rhai/engine.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule Rhai.Engine do
@moduledoc """
Rhai main scripting engine.
"""

defstruct [
# The actual NIF Resource.
resource: nil,
# Normally the compiler will happily do stuff like inlining the
# resource in attributes. This will convert the resource into an
# empty binary with no warning. This will make that harder to
# accidentaly do.
# It also serves as a handy way to tell file handles apart.
reference: nil
]

@type t :: %__MODULE__{}

@doc """
Create a new Engine
"""
@spec new :: t()
def new do
wrap_resource(Rhai.Native.engine_new())
end

@doc """
Evaluate a string as a script, returning the result value or an error.
"""
@spec eval(t(), String.t()) :: {:ok, Rhai.rhai_any()} | {:error, Rhai.rhai_error()}
def eval(%__MODULE__{resource: resource}, script) do
Rhai.Native.engine_eval(resource, script)
end

@doc """
Set whether to raise error if an object map property does not exist.
"""
@spec set_fail_on_invalid_map_property(t(), boolean) :: t()
def set_fail_on_invalid_map_property(%__MODULE__{resource: resource} = engine, enable) do
Rhai.Native.engine_set_fail_on_invalid_map_property(resource, enable)

engine
end

@doc """
Set whether to raise error if an object map property does not exist.
"""
@spec fail_on_invalid_map_property?(t()) :: boolean
def fail_on_invalid_map_property?(%__MODULE__{resource: resource}) do
Rhai.Native.engine_fail_on_invalid_map_property(resource)
end

defp wrap_resource(resource) do
%__MODULE__{
resource: resource,
reference: make_ref()
}
end
end
12 changes: 9 additions & 3 deletions lib/rhai/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ defmodule Rhai.Native do
version: version,
targets: targets

def eval(_, _) do
:erlang.nif_error(:nif_not_loaded)
end
# legacy
def eval(_, _), do: err()
# engine
def engine_new, do: err()
def engine_eval(_engine, _script), do: err()
def engine_set_fail_on_invalid_map_property(_engine, _flag), do: err()
def engine_fail_on_invalid_map_property(_engine), do: err()

defp err, do: :erlang.nif_error(:nif_not_loaded)
end
2 changes: 1 addition & 1 deletion native/rhai_rustler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ crate-type = ["cdylib"]

[dependencies]
rustler = "0.27.0"
rhai = "1.12.0"
rhai = { version ="1.12.0", features = ["sync"] }
45 changes: 45 additions & 0 deletions native/rhai_rustler/src/engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use std::sync::Mutex;

use rhai::{Dynamic, Engine};
use rustler::{Env, ResourceArc, Term};

use crate::{errors::to_error, types::from_dynamic};

pub struct EngineResource {
pub engine: Mutex<Engine>,
}

#[rustler::nif]
fn engine_new() -> ResourceArc<EngineResource> {
ResourceArc::new(EngineResource {
engine: Mutex::new(Engine::new()),
})
}

#[rustler::nif]
fn engine_eval<'a>(
env: Env<'a>,
resource: ResourceArc<EngineResource>,
script: &str,
) -> Result<Term<'a>, Term<'a>> {
let engine = resource.engine.try_lock().unwrap();

match engine.eval::<Dynamic>(script) {
Ok(result) => Ok(from_dynamic(env, result)),
Err(e) => Err(to_error(env, *e)),
}
}

#[rustler::nif]
fn engine_set_fail_on_invalid_map_property(resource: ResourceArc<EngineResource>, enable: bool) {
let mut engine = resource.engine.try_lock().unwrap();

engine.set_fail_on_invalid_map_property(enable);
}

#[rustler::nif]
fn engine_fail_on_invalid_map_property(resource: ResourceArc<EngineResource>) -> bool {
let engine = resource.engine.try_lock().unwrap();

engine.fail_on_invalid_map_property()
}
22 changes: 21 additions & 1 deletion native/rhai_rustler/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod engine;
mod errors;
mod types;

Expand All @@ -6,6 +7,8 @@ use std::collections::HashMap;
use rhai::{Dynamic, Engine, Scope};
use rustler::{Env, Term};

use crate::engine::*;

#[rustler::nif]
fn eval<'a>(
env: Env<'a>,
Expand All @@ -31,4 +34,21 @@ fn eval<'a>(
}
}

rustler::init!("Elixir.Rhai.Native", [eval]); //, load = load);
fn load(env: Env, _: Term) -> bool {
rustler::resource!(EngineResource, env);
true
}

rustler::init!(
"Elixir.Rhai.Native",
[
// legacy
eval,
// engine
engine_new,
engine_eval,
engine_set_fail_on_invalid_map_property,
engine_fail_on_invalid_map_property
],
load = load
);
33 changes: 33 additions & 0 deletions test/rhai/engine_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
defmodule Rhai.EngineTest do
use ExUnit.Case

alias Rhai.Engine

describe "new/0" do
test "should create a new engine" do
assert %Engine{} = Engine.new()
end
end

describe "eval/1" do
test "should eval a script" do
engine = Engine.new()

assert {:ok, 2} = Engine.eval(engine, "1 + 1")
end
end

describe "set_fail_on_invalid_map_property/2, fail_on_invalid_map_property?/0" do
test "should return false by default" do
engine = Engine.new()

refute Engine.fail_on_invalid_map_property?(engine)
end

test "should set fail on invalid map property to enabled" do
assert Engine.new()
|> Engine.set_fail_on_invalid_map_property(true)
|> Engine.fail_on_invalid_map_property?()
end
end
end

0 comments on commit c3aa26f

Please sign in to comment.