use rustler::types::{atom, tuple, MapIterator};
#[cfg(feature = "nif")]
use rustler::ResourceArc;
use rustler::{Atom, Decoder, Encoder, Env, Error, NifResult, Resource, Term};
#[derive(Clone, Copy)]
pub enum ValueRef<'a> {
Atom(Atom),
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Str(&'a str),
StringList(&'a [String]),
Term(Term<'a>),
}
impl<'a> ValueRef<'a> {
pub fn encode<'env>(self, env: Env<'env>) -> Term<'env> {
match self {
Self::Atom(value) => value.encode(env),
Self::Bool(value) => value.encode(env),
Self::I64(value) => value.encode(env),
Self::U64(value) => value.encode(env),
Self::F64(value) => value.encode(env),
Self::Str(value) => value.encode(env),
Self::StringList(value) => value.encode(env),
Self::Term(value) => value.in_env(env),
}
}
}
pub trait MatchEvent<'a> {
fn tag(&self) -> Atom;
fn arity(&self) -> usize;
fn positional_field(&self, index: usize) -> Option<ValueRef<'a>>;
fn field(&self, name: Atom) -> Option<ValueRef<'a>>;
}
#[derive(Debug, Default)]
pub struct Selector {
clauses: Vec<Clause>,
variable_count: usize,
}
#[rustler::resource_impl]
impl Resource for Selector {}
#[cfg(feature = "nif")]
struct SelectorSource(ResourceArc<Selector>);
#[cfg(feature = "nif")]
impl<'a> Decoder<'a> for SelectorSource {
fn decode(term: Term<'a>) -> NifResult<Self> {
match term.decode::<ResourceArc<Selector>>() {
Ok(selector) => Ok(Self(selector)),
Err(_error) => Selector::decode(term).map(ResourceArc::new).map(Self),
}
}
}
impl<'a> Decoder<'a> for Selector {
fn decode(term: Term<'a>) -> NifResult<Self> {
let clauses = term
.decode::<Vec<Term>>()?
.into_iter()
.map(Clause::decode)
.collect::<NifResult<Vec<_>>>()?;
let variable_count = clauses
.iter()
.map(Clause::variable_count)
.max()
.unwrap_or(0);
Ok(Self {
clauses,
variable_count,
})
}
}
impl Selector {
pub fn from_term(term: Term<'_>) -> NifResult<Self> {
term.decode()
}
pub fn run_term_event<'a>(
&self,
env: Env<'a>,
event: Term<'a>,
out: &mut Vec<Term<'a>>,
) -> NifResult<()> {
for clause in &self.clauses {
let mut bindings = vec![None; self.variable_count + 1];
if !match_pattern(&clause.head, event, &mut bindings)? {
continue;
}
if !clause
.guards
.iter()
.map(|guard| eval_guard(guard, env, &bindings))
.collect::<NifResult<Vec<_>>>()?
.into_iter()
.all(|passes| passes)
{
continue;
}
for projection in &clause.body {
out.push(project(projection, env, &bindings)?);
}
}
Ok(())
}
pub fn run_event<'env, 'event, E>(
&self,
env: Env<'env>,
event: &E,
out: &mut Vec<Term<'env>>,
) -> NifResult<()>
where
E: MatchEvent<'event>,
{
for clause in &self.clauses {
let mut bindings = vec![None; self.variable_count + 1];
if !match_event_pattern(&clause.head, event, &mut bindings)? {
continue;
}
if !clause
.guards
.iter()
.map(|guard| eval_event_guard(guard, env, &bindings))
.collect::<NifResult<Vec<_>>>()?
.into_iter()
.all(|passes| passes)
{
continue;
}
for projection in &clause.body {
out.push(project_event(projection, env, &bindings)?);
}
}
Ok(())
}
pub fn is_empty(&self) -> bool {
self.clauses.is_empty()
}
}
#[derive(Debug)]
struct Clause {
head: Pattern,
guards: Vec<Expr>,
body: Vec<Expr>,
}
impl<'a> Decoder<'a> for Clause {
fn decode(term: Term<'a>) -> NifResult<Self> {
let (head, guards, body): (Term, Vec<Term>, Vec<Term>) = term.decode()?;
Ok(Self {
head: Pattern::from_term(head)?,
guards: guards
.into_iter()
.map(Expr::from_term)
.collect::<NifResult<Vec<_>>>()?,
body: body
.into_iter()
.map(Expr::from_term)
.collect::<NifResult<Vec<_>>>()?,
})
}
}
impl Clause {
fn variable_count(&self) -> usize {
self.head
.variable_count()
.max(
self.guards
.iter()
.map(Expr::variable_count)
.max()
.unwrap_or(0),
)
.max(
self.body
.iter()
.map(Expr::variable_count)
.max()
.unwrap_or(0),
)
}
}
#[derive(Debug, Clone)]
enum Pattern {
Wildcard,
Var(usize),
Literal(Literal),
Tuple(Vec<Pattern>),
List(Vec<Pattern>),
Map(Vec<(Expr, Pattern)>),
}
impl Pattern {
fn from_term(term: Term<'_>) -> NifResult<Self> {
if let Some(var) = variable_index(term)? {
return Ok(Self::Var(var));
}
if is_wildcard(term)? {
return Ok(Self::Wildcard);
}
if term.is_tuple() {
return tuple::get_tuple(term)?
.into_iter()
.map(Self::from_term)
.collect::<NifResult<Vec<_>>>()
.map(Self::Tuple);
}
if term.is_empty_list() || term.is_list() {
return term
.decode::<Vec<Term>>()?
.into_iter()
.map(Self::from_term)
.collect::<NifResult<Vec<_>>>()
.map(Self::List);
}
if term.is_map() {
return term
.decode::<MapIterator>()?
.map(|(key, value)| Ok((Expr::from_term(key)?, Self::from_term(value)?)))
.collect::<NifResult<Vec<_>>>()
.map(Self::Map);
}
Literal::from_term(term).map(Self::Literal)
}
fn variable_count(&self) -> usize {
match self {
Self::Wildcard | Self::Literal(_) => 0,
Self::Var(index) => *index,
Self::Tuple(items) | Self::List(items) => {
items.iter().map(Self::variable_count).max().unwrap_or(0)
}
Self::Map(pairs) => pairs
.iter()
.map(|(key, value)| key.variable_count().max(value.variable_count()))
.max()
.unwrap_or(0),
}
}
}
#[derive(Debug, Clone)]
enum Expr {
Var(usize),
Literal(Literal),
Tuple(Vec<Expr>),
List(Vec<Expr>),
Map(Vec<(Expr, Expr)>),
}
impl Expr {
fn from_term(term: Term<'_>) -> NifResult<Self> {
if let Some(var) = variable_index(term)? {
return Ok(Self::Var(var));
}
if term.is_tuple() {
return tuple::get_tuple(term)?
.into_iter()
.map(Self::from_term)
.collect::<NifResult<Vec<_>>>()
.map(Self::Tuple);
}
if term.is_empty_list() || term.is_list() {
return term
.decode::<Vec<Term>>()?
.into_iter()
.map(Self::from_term)
.collect::<NifResult<Vec<_>>>()
.map(Self::List);
}
if term.is_map() {
return term
.decode::<MapIterator>()?
.map(|(key, value)| Ok((Self::from_term(key)?, Self::from_term(value)?)))
.collect::<NifResult<Vec<_>>>()
.map(Self::Map);
}
Literal::from_term(term).map(Self::Literal)
}
fn variable_count(&self) -> usize {
match self {
Self::Var(index) => *index,
Self::Literal(_) => 0,
Self::Tuple(items) | Self::List(items) => {
items.iter().map(Self::variable_count).max().unwrap_or(0)
}
Self::Map(pairs) => pairs
.iter()
.map(|(key, value)| key.variable_count().max(value.variable_count()))
.max()
.unwrap_or(0),
}
}
}
#[derive(Debug, Clone)]
enum Literal {
Atom(Atom),
Bool(bool),
I64(i64),
U64(u64),
F64(f64),
Binary(String),
}
impl Literal {
fn from_term(term: Term<'_>) -> NifResult<Self> {
if let Ok(value) = term.decode::<bool>() {
return Ok(Self::Bool(value));
}
if term.is_atom() {
return term.decode::<Atom>().map(Self::Atom);
}
if let Ok(value) = term.decode::<i64>() {
return Ok(Self::I64(value));
}
if let Ok(value) = term.decode::<u64>() {
return Ok(Self::U64(value));
}
if let Ok(value) = term.decode::<f64>() {
return Ok(Self::F64(value));
}
if term.is_binary() {
return term.decode::<String>().map(Self::Binary);
}
Err(Error::BadArg)
}
fn encode<'a>(&self, env: Env<'a>) -> Term<'a> {
match self {
Self::Atom(value) => value.encode(env),
Self::Bool(value) => value.encode(env),
Self::I64(value) => value.encode(env),
Self::U64(value) => value.encode(env),
Self::F64(value) => value.encode(env),
Self::Binary(value) => value.encode(env),
}
}
}
type Bindings<'a> = [Option<Term<'a>>];
type EventBindings<'a> = [Option<ValueRef<'a>>];
fn match_event_pattern<'a, E>(
pattern: &Pattern,
event: &E,
bindings: &mut EventBindings<'a>,
) -> NifResult<bool>
where
E: MatchEvent<'a>,
{
let Pattern::Tuple(items) = pattern else {
return Ok(false);
};
if items.len() != event.arity() {
return Ok(false);
}
if let Some(first) = items.first() {
if !match_event_value(first, ValueRef::Atom(event.tag()), bindings)? {
return Ok(false);
}
}
for (index, pattern) in items.iter().enumerate().skip(1) {
let Some(value) = event.positional_field(index) else {
return Ok(false);
};
if !match_event_value(pattern, value, bindings)? {
return Ok(false);
}
}
Ok(true)
}
fn match_event_value<'a>(
pattern: &Pattern,
value: ValueRef<'a>,
bindings: &mut EventBindings<'a>,
) -> NifResult<bool> {
match pattern {
Pattern::Wildcard => Ok(true),
Pattern::Var(index) => bind_event_variable(*index, value, bindings),
Pattern::Literal(literal) => Ok(literal_matches_value_ref(literal, value)),
Pattern::Tuple(_) | Pattern::List(_) | Pattern::Map(_) => Ok(false),
}
}
fn bind_event_variable<'a>(
index: usize,
value: ValueRef<'a>,
bindings: &mut EventBindings<'a>,
) -> NifResult<bool> {
let Some(slot) = bindings.get_mut(index) else {
return Err(Error::BadArg);
};
match slot {
Some(bound) => Ok(value_ref_equal(*bound, value)),
None => {
*slot = Some(value);
Ok(true)
}
}
}
fn literal_matches_value_ref(literal: &Literal, value: ValueRef<'_>) -> bool {
match (literal, value) {
(Literal::Atom(left), ValueRef::Atom(right)) => *left == right,
(Literal::Bool(left), ValueRef::Bool(right)) => *left == right,
(Literal::I64(left), ValueRef::I64(right)) => *left == right,
(Literal::U64(left), ValueRef::U64(right)) => *left == right,
(Literal::F64(left), ValueRef::F64(right)) => *left == right,
(Literal::Binary(left), ValueRef::Str(right)) => left == right,
_ => false,
}
}
fn value_ref_equal<'a>(left: ValueRef<'a>, right: ValueRef<'a>) -> bool {
match (left, right) {
(ValueRef::Atom(left), ValueRef::Atom(right)) => left == right,
(ValueRef::Bool(left), ValueRef::Bool(right)) => left == right,
(ValueRef::I64(left), ValueRef::I64(right)) => left == right,
(ValueRef::U64(left), ValueRef::U64(right)) => left == right,
(ValueRef::F64(left), ValueRef::F64(right)) => left == right,
(ValueRef::Str(left), ValueRef::Str(right)) => left == right,
(ValueRef::StringList(left), ValueRef::StringList(right)) => left == right,
(ValueRef::Term(left), ValueRef::Term(right)) => left == right,
_ => false,
}
}
fn match_pattern<'a>(
pattern: &Pattern,
term: Term<'a>,
bindings: &mut Bindings<'a>,
) -> NifResult<bool> {
match pattern {
Pattern::Wildcard => Ok(true),
Pattern::Var(index) => bind_variable(*index, term, bindings),
Pattern::Literal(literal) => Ok(literal.encode(term.get_env()) == term),
Pattern::Tuple(items) => {
if !term.is_tuple() {
return Ok(false);
}
let values = tuple::get_tuple(term)?;
if values.len() != items.len() {
return Ok(false);
}
for (pattern, value) in items.iter().zip(values) {
if !match_pattern(pattern, value, bindings)? {
return Ok(false);
}
}
Ok(true)
}
Pattern::List(items) => {
if !(term.is_empty_list() || term.is_list()) {
return Ok(false);
}
let values = term.decode::<Vec<Term>>()?;
if values.len() != items.len() {
return Ok(false);
}
for (pattern, value) in items.iter().zip(values) {
if !match_pattern(pattern, value, bindings)? {
return Ok(false);
}
}
Ok(true)
}
Pattern::Map(pairs) => {
if !term.is_map() {
return Ok(false);
}
for (key, pattern) in pairs {
let key = project(key, term.get_env(), bindings)?;
let Ok(value) = term.map_get(key) else {
return Ok(false);
};
if !match_pattern(pattern, value, bindings)? {
return Ok(false);
}
}
Ok(true)
}
}
}
fn bind_variable<'a>(index: usize, term: Term<'a>, bindings: &mut Bindings<'a>) -> NifResult<bool> {
let Some(slot) = bindings.get_mut(index) else {
return Err(Error::BadArg);
};
match slot {
Some(bound) => Ok(*bound == term),
None => {
*slot = Some(term);
Ok(true)
}
}
}
fn eval_guard<'a>(expr: &Expr, env: Env<'a>, bindings: &Bindings<'a>) -> NifResult<bool> {
let Expr::Tuple(items) = expr else {
return Ok(is_truthy(project(expr, env, bindings)?));
};
let Some(Expr::Literal(Literal::Atom(op))) = items.first() else {
return Ok(is_truthy(project(expr, env, bindings)?));
};
let op = op.to_term(env).atom_to_string()?;
match (op.as_str(), &items[1..]) {
("is_atom", [value]) => Ok(project(value, env, bindings)?.is_atom()),
("is_binary", [value]) => Ok(project(value, env, bindings)?.is_binary()),
("is_boolean", [value]) => Ok(project(value, env, bindings)?.decode::<bool>().is_ok()),
("is_float", [value]) => Ok(project(value, env, bindings)?.is_float()),
("is_integer", [value]) => Ok(project(value, env, bindings)?.is_integer()),
("is_list", [value]) => {
let value = project(value, env, bindings)?;
Ok(value.is_list() || value.is_empty_list())
}
("is_map", [value]) => Ok(project(value, env, bindings)?.is_map()),
("is_number", [value]) => Ok(project(value, env, bindings)?.is_number()),
("is_tuple", [value]) => Ok(project(value, env, bindings)?.is_tuple()),
("not", [value]) => Ok(!eval_guard(value, env, bindings)?),
("and", [left, right]) | ("andalso", [left, right]) => {
Ok(eval_guard(left, env, bindings)? && eval_guard(right, env, bindings)?)
}
("or", [left, right]) | ("orelse", [left, right]) => {
Ok(eval_guard(left, env, bindings)? || eval_guard(right, env, bindings)?)
}
("xor", [left, right]) => {
Ok(eval_guard(left, env, bindings)? ^ eval_guard(right, env, bindings)?)
}
("==", [left, right]) | ("=:=", [left, right]) => {
Ok(project(left, env, bindings)? == project(right, env, bindings)?)
}
("/=", [left, right]) | ("=/=", [left, right]) => {
Ok(project(left, env, bindings)? != project(right, env, bindings)?)
}
(">", [left, right]) => Ok(project(left, env, bindings)? > project(right, env, bindings)?),
(">=", [left, right]) => {
Ok(project(left, env, bindings)? >= project(right, env, bindings)?)
}
("<", [left, right]) => Ok(project(left, env, bindings)? < project(right, env, bindings)?),
("=<", [left, right]) => {
Ok(project(left, env, bindings)? <= project(right, env, bindings)?)
}
("member", [left, right]) => member(
project(left, env, bindings)?,
project(right, env, bindings)?,
),
_ => Err(Error::BadArg),
}
}
fn eval_event_guard<'env, 'event>(
expr: &Expr,
env: Env<'env>,
bindings: &EventBindings<'event>,
) -> NifResult<bool> {
let Expr::Tuple(items) = expr else {
return Ok(is_truthy(project_event(expr, env, bindings)?));
};
let Some(Expr::Literal(Literal::Atom(op))) = items.first() else {
return Ok(is_truthy(project_event(expr, env, bindings)?));
};
let op = op.to_term(env).atom_to_string()?;
match (op.as_str(), &items[1..]) {
("is_atom", [value]) => Ok(project_event(value, env, bindings)?.is_atom()),
("is_binary", [value]) => Ok(project_event(value, env, bindings)?.is_binary()),
("is_boolean", [value]) => Ok(project_event(value, env, bindings)?
.decode::<bool>()
.is_ok()),
("is_float", [value]) => Ok(project_event(value, env, bindings)?.is_float()),
("is_integer", [value]) => Ok(project_event(value, env, bindings)?.is_integer()),
("is_list", [value]) => {
let value = project_event(value, env, bindings)?;
Ok(value.is_list() || value.is_empty_list())
}
("is_map", [value]) => Ok(project_event(value, env, bindings)?.is_map()),
("is_number", [value]) => Ok(project_event(value, env, bindings)?.is_number()),
("is_tuple", [value]) => Ok(project_event(value, env, bindings)?.is_tuple()),
("not", [value]) => Ok(!eval_event_guard(value, env, bindings)?),
("and", [left, right]) | ("andalso", [left, right]) => {
Ok(eval_event_guard(left, env, bindings)? && eval_event_guard(right, env, bindings)?)
}
("or", [left, right]) | ("orelse", [left, right]) => {
Ok(eval_event_guard(left, env, bindings)? || eval_event_guard(right, env, bindings)?)
}
("xor", [left, right]) => {
Ok(eval_event_guard(left, env, bindings)? ^ eval_event_guard(right, env, bindings)?)
}
("==", [left, right]) | ("=:=", [left, right]) => {
Ok(project_event(left, env, bindings)? == project_event(right, env, bindings)?)
}
("/=", [left, right]) | ("=/=", [left, right]) => {
Ok(project_event(left, env, bindings)? != project_event(right, env, bindings)?)
}
(">", [left, right]) => {
Ok(project_event(left, env, bindings)? > project_event(right, env, bindings)?)
}
(">=", [left, right]) => {
Ok(project_event(left, env, bindings)? >= project_event(right, env, bindings)?)
}
("<", [left, right]) => {
Ok(project_event(left, env, bindings)? < project_event(right, env, bindings)?)
}
("=<", [left, right]) => {
Ok(project_event(left, env, bindings)? <= project_event(right, env, bindings)?)
}
("member", [left, right]) => member(
project_event(left, env, bindings)?,
project_event(right, env, bindings)?,
),
_ => Err(Error::BadArg),
}
}
fn member<'a>(left: Term<'a>, right: Term<'a>) -> NifResult<bool> {
if !(right.is_empty_list() || right.is_list()) {
return Err(Error::BadArg);
}
Ok(right
.decode::<Vec<Term>>()?
.into_iter()
.any(|term| term == left))
}
fn project<'a>(expr: &Expr, env: Env<'a>, bindings: &Bindings<'a>) -> NifResult<Term<'a>> {
match expr {
Expr::Var(index) => bindings
.get(*index)
.and_then(|value| *value)
.ok_or(Error::BadArg),
Expr::Literal(literal) => Ok(literal.encode(env)),
Expr::Tuple(items) => items
.iter()
.map(|item| project(item, env, bindings))
.collect::<NifResult<Vec<_>>>()
.map(|items| tuple::make_tuple(env, &items)),
Expr::List(items) => items
.iter()
.map(|item| project(item, env, bindings))
.collect::<NifResult<Vec<_>>>()
.map(|items| items.encode(env)),
Expr::Map(pairs) => {
let projected = pairs
.iter()
.map(|(key, value)| {
Ok((project(key, env, bindings)?, project(value, env, bindings)?))
})
.collect::<NifResult<Vec<_>>>()?;
Term::map_from_term_arrays(
env,
&projected
.iter()
.map(|(key, _value)| *key)
.collect::<Vec<_>>(),
&projected
.iter()
.map(|(_key, value)| *value)
.collect::<Vec<_>>(),
)
}
}
}
fn project_event<'env, 'event>(
expr: &Expr,
env: Env<'env>,
bindings: &EventBindings<'event>,
) -> NifResult<Term<'env>> {
match expr {
Expr::Var(index) => match bindings.get(*index) {
Some(Some(value)) => Ok(value.encode(env)),
_ => Err(Error::BadArg),
},
Expr::Literal(literal) => Ok(literal.encode(env)),
Expr::Tuple(items) => items
.iter()
.map(|item| project_event(item, env, bindings))
.collect::<NifResult<Vec<_>>>()
.map(|items| tuple::make_tuple(env, &items)),
Expr::List(items) => items
.iter()
.map(|item| project_event(item, env, bindings))
.collect::<NifResult<Vec<_>>>()
.map(|items| items.encode(env)),
Expr::Map(pairs) => {
let projected = pairs
.iter()
.map(|(key, value)| {
Ok((
project_event(key, env, bindings)?,
project_event(value, env, bindings)?,
))
})
.collect::<NifResult<Vec<_>>>()?;
Term::map_from_term_arrays(
env,
&projected
.iter()
.map(|(key, _value)| *key)
.collect::<Vec<_>>(),
&projected
.iter()
.map(|(_key, value)| *value)
.collect::<Vec<_>>(),
)
}
}
}
fn is_truthy(term: Term<'_>) -> bool {
atom::is_truthy(term)
}
fn variable_index(term: Term<'_>) -> NifResult<Option<usize>> {
if !term.is_atom() {
return Ok(None);
}
let value = term.atom_to_string()?;
if let Some(rest) = value.strip_prefix('$') {
return rest.parse::<usize>().map(Some).map_err(|_| Error::BadArg);
}
Ok(None)
}
fn is_wildcard(term: Term<'_>) -> NifResult<bool> {
Ok(term.is_atom() && term.atom_to_string()? == "_")
}
#[cfg(feature = "nif")]
#[rustler::nif]
fn compile(selector: Selector) -> ResourceArc<Selector> {
ResourceArc::new(selector)
}
#[cfg(feature = "nif")]
#[rustler::nif]
fn select<'a>(
env: Env<'a>,
events: Vec<Term<'a>>,
selector: SelectorSource,
) -> NifResult<Vec<Term<'a>>> {
let mut out = Vec::new();
for event in events {
selector.0.run_term_event(env, event, &mut out)?;
}
Ok(out)
}
#[cfg(feature = "nif")]
rustler::init!("Elixir.RustlerMatchSpec.Native");