pub(crate) mod analyzer;
pub mod base_expr;
pub(crate) mod binary;
pub mod deploy;
pub mod eq;
pub mod helper;
pub mod module;
mod node_id;
pub mod query;
pub(crate) mod raw;
mod support;
mod upable;
pub mod visitors;
pub mod docs;
pub mod walkers;
pub mod warning;
pub use self::helper::Helper;
pub use self::node_id::{BaseRef, NodeId};
use self::walkers::ImutExprWalker;
use self::{base_expr::Ranged, visitors::ConstFolder};
use crate::{
arena,
ast::{
eq::AstEq,
raw::{BytesDataType, Endian},
},
errors::{err_generic, error_no_locals, Kind as ErrorKind, Result},
extractor::Extractor,
impl_expr, impl_expr_ex, impl_expr_no_lt,
interpreter::{AggrType, Cont, Env, ExecOpts, LocalStack},
lexer::Span,
pos::Location,
prelude::*,
registry::{CustomFn, FResult, TremorAggrFnWrapper, TremorFnWrapper},
script::Return,
stry, KnownKey, Value,
};
pub(crate) use analyzer::*;
pub use base_expr::BaseExpr;
use beef::Cow;
pub use deploy::*;
use halfbrown::HashMap;
pub use query::*;
use serde::Serialize;
use std::{collections::BTreeMap, mem};
use upable::Upable;
pub(crate) type Exprs<'script> = Vec<Expr<'script>>;
pub(crate) type ImutExprs<'script> = Vec<ImutExpr<'script>>;
pub(crate) type Fields<'script> = Vec<Field<'script>>;
pub(crate) type Segments<'script> = Vec<Segment<'script>>;
pub(crate) type PatternFields<'script> = Vec<PredicatePattern<'script>>;
pub(crate) type Predicates<'script, Ex> = Vec<ClauseGroup<'script, Ex>>;
pub(crate) type PatchOperations<'script> = Vec<PatchOperation<'script>>;
pub(crate) type ComprehensionCases<'script, Ex> = Vec<ComprehensionCase<'script, Ex>>;
pub(crate) type ArrayPredicatePatterns<'script> = Vec<ArrayPredicatePattern<'script>>;
pub type Stmts<'script> = Vec<Stmt<'script>>;
pub trait Expression: Clone + std::fmt::Debug + PartialEq + Serialize {
fn replace_last_shadow_use(&mut self, replace_idx: usize);
fn is_null_lit(&self) -> bool;
fn null_lit(mid: Box<NodeMeta>) -> Self;
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct NodeMeta {
range: Span,
name: Option<String>,
}
impl std::fmt::Debug for NodeMeta {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(name) = &self.name {
write!(f, "{name}:")?;
}
self.range.fmt(f)
}
}
impl NodeMeta {
pub(crate) fn new_box(start: Location, end: Location) -> Box<Self> {
Box::new(Self::new(start, end))
}
#[must_use]
pub fn new(start: Location, end: Location) -> Self {
NodeMeta {
range: Span::new(start, end),
name: None,
}
}
#[cfg(test)]
pub(crate) fn dummy() -> Box<Self> {
Box::new(NodeMeta::new(
Location::start_of_file(arena::Index::INVALID),
Location::start_of_file(arena::Index::INVALID),
))
}
pub(crate) fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub(crate) fn set_name<S>(&mut self, name: &S)
where
S: ToString + ?Sized,
{
self.name = Some(name.to_string());
}
pub(crate) fn with_name<S>(mut self, name: &S) -> Self
where
S: ToString + ?Sized,
{
self.name = Some(name.to_string());
self
}
pub(crate) fn box_with_name<S>(self, name: &S) -> Box<Self>
where
S: ToString + ?Sized,
{
Box::new(self.with_name(name))
}
pub(crate) fn end(&self) -> Location {
self.range.end()
}
pub(crate) fn start(&self) -> Location {
self.range.start()
}
pub(crate) fn aid(&self) -> arena::Index {
self.range.aid()
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
struct Function<'script> {
is_const: bool,
argc: usize,
name: Cow<'script, str>,
}
#[derive(Debug, PartialEq, Serialize, Clone)]
pub struct BytesPart<'script> {
pub(crate) mid: Box<NodeMeta>,
pub(crate) data: ImutExpr<'script>,
pub(crate) data_type: BytesDataType,
pub(crate) endianess: Endian,
pub(crate) bits: u64,
}
impl_expr!(BytesPart);
impl<'script> BytesPart<'script> {
pub(crate) fn is_lit(&self) -> bool {
self.data.is_lit()
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Bytes<'script> {
mid: Box<NodeMeta>,
pub value: Vec<BytesPart<'script>>,
}
impl_expr!(Bytes);
#[derive(Clone, Copy, Debug)]
pub struct RunConsts<'run, 'script>
where
'script: 'run,
{
pub args: &'run Value<'script>,
pub group: &'run Value<'script>,
pub window: &'run Value<'script>,
}
impl<'run, 'script> RunConsts<'run, 'script>
where
'script: 'run,
{
pub(crate) fn with_new_args<'r>(&'r self, args: &'r Value<'script>) -> RunConsts<'r, 'script>
where
'run: 'r,
{
RunConsts {
args,
group: self.group,
window: self.window,
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Eq)]
pub struct Consts<'script> {
pub args: Value<'script>,
pub group: Value<'script>,
pub window: Value<'script>,
}
impl<'script> Consts<'script> {
#[must_use]
pub fn run(&self) -> RunConsts<'_, 'script> {
RunConsts {
args: &self.args,
group: &self.group,
window: &self.window,
}
}
pub(crate) const fn new() -> Self {
Consts {
args: Value::const_null(),
group: Value::const_null(),
window: Value::const_null(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Script<'script> {
pub(crate) mid: Box<NodeMeta>,
pub exprs: Exprs<'script>,
pub state: Option<ImutExpr<'script>>,
pub locals: usize,
#[serde(skip)]
pub docs: docs::Docs,
}
impl_expr!(Script);
impl Default for Script<'static> {
fn default() -> Self {
Self {
exprs: Vec::new(),
locals: 0,
docs: docs::Docs::default(),
mid: NodeMeta::new_box(Location::default(), Location::default()),
state: None,
}
}
}
impl<'script> Script<'script> {
pub fn run_imut<'event>(
&self,
context: &crate::EventContext,
aggr: AggrType,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
) -> Result<Return<'event>>
where
'script: 'event,
{
let local = LocalStack::with_size(self.locals);
let opts = ExecOpts {
result_needed: true,
aggr,
};
let env = Env {
context,
..Env::default()
};
self.exprs.last().map_or(Ok(Return::Drop), |expr| {
let imut = expr.as_imut()?;
let v = stry!(imut.run(opts.with_result(), &env, event, state, meta, &local));
Ok(Return::Emit {
value: v.into_owned(),
port: None,
})
})
}
pub fn run<'event>(
&self,
context: &crate::EventContext,
aggr: AggrType,
event: &mut Value<'event>,
state: &mut Value<'static>,
meta: &mut Value<'event>,
) -> Result<Return<'event>>
where
'script: 'event,
{
let mut local = LocalStack::with_size(self.locals);
let mut exprs = self.exprs.iter().peekable();
let opts = ExecOpts {
result_needed: true,
aggr,
};
let env = Env {
context,
..Env::default()
};
while let Some(expr) = exprs.next() {
if exprs.peek().is_none() {
match stry!(expr.run(opts.with_result(), &env, event, state, meta, &mut local)) {
Cont::Drop => return Ok(Return::Drop),
Cont::Emit(value, port) => return Ok(Return::Emit { value, port }),
Cont::EmitEvent(port) => {
return Ok(Return::EmitEvent { port });
}
Cont::Cont(v) => {
return Ok(Return::Emit {
value: v.into_owned(),
port: None,
})
}
}
}
match stry!(expr.run(opts.without_result(), &env, event, state, meta, &mut local)) {
Cont::Drop => return Ok(Return::Drop),
Cont::Emit(value, port) => return Ok(Return::Emit { value, port }),
Cont::EmitEvent(port) => {
return Ok(Return::EmitEvent { port });
}
Cont::Cont(_v) => (),
}
}
unreachable!()
}
}
#[derive(Debug, PartialEq, Serialize, Clone, Eq)]
pub struct Ident<'script> {
pub(crate) mid: Box<NodeMeta>,
pub id: beef::Cow<'script, str>,
}
impl<'script> Ident<'script> {
#[must_use]
pub fn new(id: beef::Cow<'script, str>, mid: Box<NodeMeta>) -> Self {
Self { mid, id }
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.id
}
}
impl_expr!(Ident);
impl<'script> std::fmt::Display for Ident<'script> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.id)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Field<'script> {
pub(crate) mid: Box<NodeMeta>,
pub name: StringLit<'script>,
pub value: ImutExpr<'script>,
}
impl_expr!(Field);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ArrayAppend<'script> {
pub(crate) left: Box<ImutExpr<'script>>,
pub(crate) right: ImutExprs<'script>,
pub(crate) mid: Box<NodeMeta>,
}
impl_expr!(ArrayAppend);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Record<'script> {
pub(crate) mid: Box<NodeMeta>,
pub base: crate::Object<'script>,
pub fields: Fields<'script>,
}
impl_expr!(Record);
impl<'script> Record<'script> {
#[must_use]
pub fn cloned_field_expr(&self, name: &str) -> Option<ImutExpr> {
self.base
.get(name)
.map(|base_value| ImutExpr::literal(self.mid.clone(), base_value.clone()))
.or_else(|| {
self.fields.iter().find_map(|f| {
f.name.as_str().and_then(|n| {
if n == name {
Some(f.value.clone())
} else {
None
}
})
})
})
}
#[must_use]
pub fn cloned_field_literal(&self, name: &str) -> Option<Value> {
if let Some(ImutExpr::Literal(Literal { value, .. })) = self.cloned_field_expr(name) {
Some(value)
} else {
self.base.get(name).cloned()
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct List<'script> {
pub(crate) mid: Box<NodeMeta>,
pub exprs: ImutExprs<'script>,
}
impl_expr!(List);
#[derive(Clone, Debug, PartialEq, Serialize, Eq)]
pub struct Literal<'script> {
pub mid: Box<NodeMeta>,
pub value: Value<'script>,
}
impl<'script> Literal<'script> {
pub(crate) fn boxed_expr(mid: Box<NodeMeta>, value: Value<'script>) -> Box<ImutExpr<'script>> {
Box::new(ImutExpr::literal(mid, value))
}
pub(crate) fn null(mid: Box<NodeMeta>) -> Self {
Self {
mid,
value: Value::null(),
}
}
}
impl_expr!(Literal);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct FnDefn<'script> {
pub(crate) mid: Box<NodeMeta>,
pub name: String,
pub(crate) args: Vec<Ident<'script>>,
pub(crate) body: Exprs<'script>,
pub(crate) locals: usize,
pub(crate) open: bool,
pub(crate) inline: bool,
}
impl_expr!(FnDefn);
#[derive(Clone, Debug, PartialEq, Serialize, Eq)]
pub struct Const<'script> {
pub mid: Box<NodeMeta>,
pub value: Value<'script>,
pub id: String,
}
impl_expr!(Const);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum Expr<'script> {
Match(Box<Match<'script, Self>>),
IfElse(Box<IfElse<'script, Self>>),
Assign {
mid: Box<NodeMeta>,
path: Path<'script>,
expr: Box<Self>,
},
AssignMoveLocal {
mid: Box<NodeMeta>,
path: Path<'script>,
idx: usize,
},
Comprehension(Box<Comprehension<'script, Self>>),
Drop {
mid: Box<NodeMeta>,
},
Emit(Box<EmitExpr<'script>>),
Imut(ImutExpr<'script>),
}
impl<'script> Expr<'script> {
const NOT_IMUT: &'static str = "Not an imutable expression";
#[must_use]
pub fn as_invoke(&self) -> Option<&Invoke<'script>> {
match self {
Expr::Imut(i) => i.as_invoke(),
_ => None,
}
}
fn as_imut(&self) -> Result<&ImutExpr<'script>> {
if let Expr::Imut(imut) = self {
Ok(imut)
} else {
let e = self.extent();
err_generic(&e.expand_lines(2), self, &Self::NOT_IMUT)
}
}
}
impl<'script> Expression for Expr<'script> {
fn replace_last_shadow_use(&mut self, replace_idx: usize) {
match self {
Expr::Assign { path, expr, mid } => match expr.as_ref() {
Expr::Imut(ImutExpr::Local { idx, .. }) if idx == &replace_idx => {
*self = Expr::AssignMoveLocal {
mid: mid.clone(),
idx: *idx,
path: path.clone(),
};
}
_ => (),
},
Expr::Match(m) => {
for cg in &mut m.patterns {
cg.replace_last_shadow_use(replace_idx);
}
}
_ => (),
}
}
fn is_null_lit(&self) -> bool {
matches!(self, Expr::Imut(ImutExpr::Literal(Literal { value, .. })) if value.is_null())
}
fn null_lit(mid: Box<NodeMeta>) -> Self {
Expr::Imut(ImutExpr::Literal(Literal::null(mid)))
}
}
impl<'script> From<ImutExpr<'script>> for Expr<'script> {
fn from(imut: ImutExpr<'script>) -> Expr<'script> {
Expr::Imut(imut)
}
}
impl<'script> From<Literal<'script>> for ImutExpr<'script> {
fn from(lit: Literal<'script>) -> Self {
ImutExpr::Literal(lit)
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ImutExpr<'script> {
Record(Record<'script>),
List(List<'script>),
Binary(Box<BinExpr<'script>>),
BinaryBoolean(Box<BooleanBinExpr<'script>>),
Unary(Box<UnaryExpr<'script>>),
Patch(Box<Patch<'script>>),
Match(Box<Match<'script, ImutExpr<'script>>>),
Comprehension(Box<Comprehension<'script, Self>>),
Merge(Box<Merge<'script>>),
Path(Path<'script>),
String(StringLit<'script>),
Local {
idx: usize,
mid: Box<NodeMeta>,
},
Literal(Literal<'script>),
Present {
path: Path<'script>,
mid: Box<NodeMeta>,
},
Invoke1(Invoke<'script>),
Invoke2(Invoke<'script>),
Invoke3(Invoke<'script>),
Invoke(Invoke<'script>),
InvokeAggr(InvokeAggr),
Recur(Recur<'script>),
Bytes(Bytes<'script>),
ArrayAppend(ArrayAppend<'script>),
}
impl<'script> ImutExpr<'script> {
fn as_invoke(&self) -> Option<&Invoke<'script>> {
use ImutExpr::{Invoke, Invoke1, Invoke2, Invoke3};
match self {
Invoke(i) | Invoke1(i) | Invoke2(i) | Invoke3(i) => Some(i),
_ => None,
}
}
pub(crate) fn literal(mid: Box<NodeMeta>, value: Value<'script>) -> Self {
ImutExpr::Literal(Literal { mid, value })
}
#[must_use]
pub fn as_record(&self) -> Option<&Record<'script>> {
if let ImutExpr::Record(r) = self {
Some(r)
} else {
None
}
}
pub(crate) fn try_into_value(mut self, helper: &Helper<'script, '_>) -> Result<Value<'script>> {
ImutExprWalker::walk_expr(&mut ConstFolder::new(helper), &mut self)?;
if let ImutExpr::Literal(Literal { value: v, .. }) = self {
Ok(v)
} else {
let e = self.extent();
Err(ErrorKind::NotConstant(e, e.expand_lines(2)).into())
}
}
#[must_use]
pub fn as_list(&self) -> Option<&List<'script>> {
if let ImutExpr::List(l) = self {
Some(l)
} else {
None
}
}
}
impl<'script> Expression for ImutExpr<'script> {
fn replace_last_shadow_use(&mut self, replace_idx: usize) {
if let ImutExpr::Match(m) = self {
for cg in &mut m.patterns {
cg.replace_last_shadow_use(replace_idx);
}
}
}
fn is_null_lit(&self) -> bool {
matches!(self, ImutExpr::Literal(Literal { value, .. }) if value.is_null())
}
fn null_lit(mid: Box<NodeMeta>) -> Self {
Self::Literal(Literal::null(mid))
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct StringLit<'script> {
pub(crate) mid: Box<NodeMeta>,
pub elements: StrLitElements<'script>,
}
#[cfg(test)]
impl<'script> From<&'script str> for StringLit<'script> {
fn from(l: &'script str) -> Self {
StringLit {
mid: NodeMeta::dummy(),
elements: vec![StrLitElement::Lit(l.into())],
}
}
}
impl<'script> StringLit<'script> {
pub(crate) fn as_str(&self) -> Option<&str> {
if let [StrLitElement::Lit(l)] = self.elements.as_slice() {
Some(l)
} else {
None
}
}
pub(crate) fn into_str(mut self) -> Option<Cow<'script, str>> {
self.as_str()?;
if let Some(StrLitElement::Lit(lit)) = self.elements.pop() {
Some(lit)
} else {
None
}
}
pub(crate) fn run<'run, 'event>(
&self,
opts: ExecOpts,
env: &Env<'run, 'event>,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
local: &LocalStack<'event>,
) -> Result<Cow<'event, str>>
where
'script: 'event,
{
if let [StrLitElement::Lit(l)] = self.elements.as_slice() {
return Ok(l.clone());
}
let mut out = String::with_capacity(128);
for e in &self.elements {
match e {
StrLitElement::Lit(l) => out.push_str(l),
#[cfg(not(feature = "erlang-float-testing"))]
StrLitElement::Expr(e) => {
let r = stry!(e.run(opts, env, event, state, meta, local));
if let Some(s) = r.as_str() {
out.push_str(s);
} else {
out.push_str(r.encode().as_str());
};
}
#[cfg(feature = "erlang-float-testing")]
crate::ast::StrLitElement::Expr(e) => {
let r = e.run(opts, env, event, state, meta, local)?;
if let Some(s) = r.as_str() {
out.push_str(s);
} else if let Some(_f) = r.as_f64() {
out.push_str("42");
} else {
out.push_str(&crate::utils::sorted_serialize(&r)?);
};
}
}
}
Ok(Cow::owned(out))
}
}
impl_expr!(StringLit);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum StrLitElement<'script> {
Lit(Cow<'script, str>),
Expr(ImutExpr<'script>),
}
impl<'script> StrLitElement<'script> {
pub(crate) fn is_lit(&self) -> bool {
match self {
StrLitElement::Lit(_) => true,
StrLitElement::Expr(e) => e.is_lit(),
}
}
pub(crate) fn as_str(&self) -> Option<&str> {
match self {
StrLitElement::Lit(l) => Some(l.as_ref()),
StrLitElement::Expr(ImutExpr::Literal(Literal { value, .. })) => value.as_str(),
StrLitElement::Expr(_) => None,
}
}
}
pub type StrLitElements<'script> = Vec<StrLitElement<'script>>;
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct EmitExpr<'script> {
pub(crate) mid: Box<NodeMeta>,
pub expr: ImutExpr<'script>,
pub port: Option<ImutExpr<'script>>,
}
impl_expr!(EmitExpr);
#[derive(Clone, Serialize)]
pub struct Invoke<'script> {
pub(crate) mid: Box<NodeMeta>,
pub node_id: NodeId,
#[serde(skip)]
pub invocable: Invocable<'script>,
pub args: ImutExprs<'script>,
}
impl_expr!(Invoke);
impl<'script> Invoke<'script> {
fn inline(self) -> Result<ImutExpr<'script>> {
self.invocable.inline(self.args, self.mid)
}
fn can_inline(&self) -> bool {
self.invocable.can_inline()
}
}
#[derive(Clone, Debug)]
pub enum Invocable<'script> {
Intrinsic(TremorFnWrapper),
Tremor(CustomFn<'script>),
}
impl<'script> Invocable<'script> {
#[must_use]
pub fn name(&self) -> &str {
match self {
Self::Intrinsic(wrapper) => wrapper.name(),
Self::Tremor(custom) => custom.name.as_str(),
}
}
fn inline(self, args: ImutExprs<'script>, mid: Box<NodeMeta>) -> Result<ImutExpr<'script>> {
match self {
Invocable::Intrinsic(_f) => Err("can't inline intrinsic".into()),
Invocable::Tremor(f) => f.inline(args, mid),
}
}
fn can_inline(&self) -> bool {
match self {
Invocable::Intrinsic(_f) => false,
Invocable::Tremor(f) => f.can_inline(),
}
}
fn is_const(&self) -> bool {
match self {
Invocable::Intrinsic(f) => f.is_const(),
Invocable::Tremor(f) => f.is_const(),
}
}
pub(crate) fn invoke<'event, 'run>(
&'run self,
env: &'run Env<'run, 'event>,
args: &'run [&'run Value<'event>],
) -> FResult<Value<'event>>
where
'script: 'event,
'event: 'run,
{
match self {
Invocable::Intrinsic(f) => f.invoke(env.context, args),
Invocable::Tremor(f) => f.invoke(env, args),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Recur<'script> {
pub(crate) mid: Box<NodeMeta>,
pub argc: usize,
pub open: bool,
pub exprs: ImutExprs<'script>,
}
impl_expr!(Recur);
#[derive(Clone, Serialize, PartialEq, Eq)]
pub struct InvokeAggr {
pub(crate) mid: Box<NodeMeta>,
pub module: String,
pub fun: String,
pub aggr_id: usize,
}
impl_expr_no_lt!(InvokeAggr);
#[derive(Clone, Serialize)]
pub struct InvokeAggrFn<'script> {
pub(crate) mid: Box<NodeMeta>,
#[serde(skip)]
pub invocable: TremorAggrFnWrapper,
pub(crate) module: String,
pub(crate) fun: String,
pub args: ImutExprs<'script>,
}
impl_expr!(InvokeAggrFn);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct TestExpr {
pub(crate) mid: Box<NodeMeta>,
pub id: String,
pub test: String,
pub extractor: Extractor,
}
impl_expr_no_lt!(TestExpr);
#[derive(Clone, Debug, PartialEq, Serialize, Eq)]
pub enum DefaultCase<Ex: Expression> {
None,
Null,
Many {
exprs: Vec<Ex>,
last_expr: Box<Ex>,
},
One(Ex),
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Match<'script, Ex: Expression + 'script> {
pub(crate) mid: Box<NodeMeta>,
pub target: ImutExpr<'script>,
pub patterns: Predicates<'script, Ex>,
pub default: DefaultCase<Ex>,
}
impl_expr_ex!(Match);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct IfElse<'script, Ex: Expression + 'script> {
pub(crate) mid: Box<NodeMeta>,
pub target: ImutExpr<'script>,
pub if_clause: PredicateClause<'script, Ex>,
pub else_clause: DefaultCase<Ex>,
}
impl_expr_ex!(IfElse);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ClausePreCondition<'script> {
pub path: Path<'script>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ClauseGroup<'script, Ex: Expression + 'script> {
Simple {
precondition: Option<ClausePreCondition<'script>>,
patterns: Vec<PredicateClause<'script, Ex>>,
},
SearchTree {
precondition: Option<ClausePreCondition<'script>>,
tree: BTreeMap<Value<'script>, (Vec<Ex>, Ex)>,
rest: Vec<PredicateClause<'script, Ex>>,
},
Combined {
precondition: Option<ClausePreCondition<'script>>,
groups: Vec<ClauseGroup<'script, Ex>>,
},
Single {
precondition: Option<ClausePreCondition<'script>>,
pattern: PredicateClause<'script, Ex>,
},
}
impl<'script, Ex: Expression + 'script> ClauseGroup<'script, Ex> {
const MAX_OPT_RUNS: u64 = 128;
const MIN_BTREE_SIZE: usize = 16;
pub(crate) fn simple(p: PredicateClause<'script, Ex>) -> Self {
ClauseGroup::Simple {
precondition: None,
patterns: vec![p],
}
}
fn combinable(&self, other: &Self) -> bool {
self.precondition().ast_eq(&other.precondition()) && self.precondition().is_some()
}
fn combine(&mut self, other: Self) {
match (self, other) {
(Self::Combined { groups, .. }, Self::Combined { groups: mut o, .. }) => {
groups.append(&mut o);
}
(Self::Combined { groups, .. }, mut other) => {
other.clear_precondition();
groups.push(other);
}
(this, other) => {
let mut precondition = None;
mem::swap(&mut precondition, this.precondition_mut());
let mut new = Self::Combined {
groups: Vec::with_capacity(2),
precondition,
};
mem::swap(&mut new, this);
this.combine(new);
this.combine(other);
}
}
}
fn clear_precondition(&mut self) {
*(self.precondition_mut()) = None;
}
pub(crate) fn precondition(&self) -> Option<&ClausePreCondition<'script>> {
match self {
ClauseGroup::Single { precondition, .. }
| ClauseGroup::Simple { precondition, .. }
| ClauseGroup::SearchTree { precondition, .. }
| ClauseGroup::Combined { precondition, .. } => precondition.as_ref(),
}
}
pub(crate) fn precondition_mut(&mut self) -> &mut Option<ClausePreCondition<'script>> {
match self {
ClauseGroup::Single { precondition, .. }
| ClauseGroup::Simple { precondition, .. }
| ClauseGroup::SearchTree { precondition, .. }
| ClauseGroup::Combined { precondition, .. } => precondition,
}
}
fn replace_last_shadow_use(&mut self, replace_idx: usize) {
match self {
Self::Simple { patterns, .. } => {
for PredicateClause { last_expr, .. } in patterns {
last_expr.replace_last_shadow_use(replace_idx);
}
}
Self::SearchTree { tree, rest, .. } => {
for p in tree.values_mut() {
p.1.replace_last_shadow_use(replace_idx);
}
for PredicateClause { last_expr, .. } in rest {
last_expr.replace_last_shadow_use(replace_idx);
}
}
Self::Combined { groups, .. } => {
for cg in groups {
cg.replace_last_shadow_use(replace_idx);
}
}
Self::Single {
pattern: PredicateClause { last_expr, .. },
..
} => last_expr.replace_last_shadow_use(replace_idx),
}
}
#[allow(
// we allow this because of the borrow checker
clippy::option_if_let_else,
clippy::too_many_lines
)]
fn optimize(&mut self, n: u64) {
if let Self::Simple {
patterns,
precondition,
} = self
{
if n > Self::MAX_OPT_RUNS {
return;
};
let mut first_key = None;
if patterns.iter().all(|p| {
match p {
PredicateClause {
pattern: Pattern::Record(RecordPattern { fields, .. }),
mid,
..
} if fields.len() == 1 => fields
.first()
.map(|f| {
match f {
PredicatePattern::Bin {
kind: BinOpKind::Eq,
key,
..
}
| PredicatePattern::TildeEq { key, .. } => {
if let Some((first, _)) = &first_key {
first == key
} else {
first_key = Some((key.clone(), mid.clone()));
true
}
}
_ => false,
}
})
.unwrap_or_default(),
_ => false,
}
}) {
if let Some((key, mid)) = &first_key {
*precondition = Some(ClausePreCondition {
path: Path::Local(LocalPath {
segments: vec![Segment::Id {
mid: mid.clone(),
key: key.clone(),
}],
idx: 0,
mid: mid.clone(),
}),
});
for pattern in patterns {
let p = match pattern {
PredicateClause {
pattern: Pattern::Record(RecordPattern { fields, .. }),
..
} => match fields.pop() {
Some(PredicatePattern::Bin { rhs, .. }) => Some(Pattern::Expr(rhs)),
Some(PredicatePattern::TildeEq { test, .. }) => {
Some(Pattern::Extract(test))
}
_other => {
unreachable!()
}
},
_ => None,
};
if let Some(p) = p {
pattern.pattern = p;
}
}
}
self.optimize(n + 1);
} else if patterns
.iter()
.filter(|p| {
matches!(
p,
PredicateClause {
pattern: Pattern::Expr(ImutExpr::Literal(_)),
guard: None,
..
}
)
})
.count()
>= Self::MIN_BTREE_SIZE
{
let mut precondition1 = None;
mem::swap(&mut precondition1, precondition);
let mut patterns1 = Vec::new();
mem::swap(&mut patterns1, patterns);
let mut rest = Vec::new();
let mut tree = BTreeMap::new();
for p in patterns1 {
match p {
PredicateClause {
pattern: Pattern::Expr(ImutExpr::Literal(Literal { value, .. })),
exprs,
last_expr,
..
} => {
tree.insert(value, (exprs, last_expr));
}
_ => rest.push(p),
}
}
*self = Self::SearchTree {
precondition: precondition1,
tree,
rest,
}
} else if patterns.len() == 1 {
if let Some(pattern) = patterns.pop() {
let mut this_precondition = None;
mem::swap(precondition, &mut this_precondition);
*self = Self::Single {
pattern,
precondition: this_precondition,
};
}
}
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct PredicateClause<'script, Ex: Expression + 'script> {
pub(crate) mid: Box<NodeMeta>,
pub pattern: Pattern<'script>,
pub guard: Option<ImutExpr<'script>>,
pub exprs: Vec<Ex>,
pub last_expr: Ex,
}
impl<'script, Ex: Expression + 'script> PredicateClause<'script, Ex> {
fn is_exclusive_to(&self, other: &Self) -> bool {
if self.guard.is_some() || other.guard.is_some() {
false
} else {
self.pattern.is_exclusive_to(&other.pattern)
}
}
}
impl_expr_ex!(PredicateClause);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ImutClauseGroup<'script> {
pub patterns: Vec<PredicateClause<'script, ImutExpr<'script>>>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Patch<'script> {
pub(crate) mid: Box<NodeMeta>,
pub target: ImutExpr<'script>,
pub operations: PatchOperations<'script>,
}
impl_expr!(Patch);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum PatchOperation<'script> {
Insert {
ident: StringLit<'script>,
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
Upsert {
ident: StringLit<'script>,
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
Update {
ident: StringLit<'script>,
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
Erase {
ident: StringLit<'script>,
mid: Box<NodeMeta>,
},
Copy {
from: StringLit<'script>,
to: StringLit<'script>,
mid: Box<NodeMeta>,
},
Move {
from: StringLit<'script>,
to: StringLit<'script>,
mid: Box<NodeMeta>,
},
Merge {
ident: StringLit<'script>,
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
MergeRecord {
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
Default {
ident: StringLit<'script>,
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
DefaultRecord {
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Merge<'script> {
pub(crate) mid: Box<NodeMeta>,
pub target: ImutExpr<'script>,
pub expr: ImutExpr<'script>,
}
impl_expr!(Merge);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct Comprehension<'script, Ex: Expression + 'script> {
pub(crate) mid: Box<NodeMeta>,
pub key_id: usize,
pub val_id: usize,
pub target: ImutExpr<'script>,
pub cases: ComprehensionCases<'script, Ex>,
}
impl_expr_ex!(Comprehension);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ComprehensionCase<'script, Ex: Expression + 'script> {
pub(crate) mid: Box<NodeMeta>,
pub key_name: Cow<'script, str>,
pub value_name: Cow<'script, str>,
pub guard: Option<ImutExpr<'script>>,
pub exprs: Vec<Ex>,
pub last_expr: Ex,
}
impl_expr_ex!(ComprehensionCase);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum Pattern<'script> {
Record(RecordPattern<'script>),
Array(ArrayPattern<'script>),
Expr(ImutExpr<'script>),
Assign(AssignPattern<'script>),
Tuple(TuplePattern<'script>),
Extract(Box<TestExpr>),
DoNotCare,
}
impl<'script> Pattern<'script> {
fn is_default(&self) -> bool {
matches!(self, Pattern::DoNotCare)
|| if let Pattern::Assign(AssignPattern { pattern, .. }) = self {
pattern.as_ref() == &Pattern::DoNotCare
} else {
false
}
}
fn is_exclusive_to(&self, other: &Self) -> bool {
use Pattern::{Assign, Expr, Record, Tuple};
match (self, other) {
(Expr(ImutExpr::Literal(l1)), Expr(ImutExpr::Literal(l2))) => !l1.ast_eq(l2),
(Record(r1), Record(r2)) => r1.is_exclusive_to(r2) || r2.is_exclusive_to(r1),
(Assign(AssignPattern { pattern, .. }), p2) => pattern.is_exclusive_to(p2),
(p1, Assign(AssignPattern { pattern, .. })) => p1.is_exclusive_to(pattern),
(Tuple(TuplePattern { exprs: e1, .. }), Tuple(TuplePattern { exprs: e2, .. })) => e1
.iter()
.zip(e2.iter())
.any(|(e1, e2)| e1.is_exclusive_to(e2) || e2.is_exclusive_to(e1)),
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum PredicatePattern<'script> {
TildeEq {
assign: Cow<'script, str>,
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
test: Box<TestExpr>,
},
Bin {
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
rhs: ImutExpr<'script>,
kind: BinOpKind,
},
RecordPatternEq {
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
pattern: RecordPattern<'script>,
},
ArrayPatternEq {
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
pattern: ArrayPattern<'script>,
},
TuplePatternEq {
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
pattern: TuplePattern<'script>,
},
FieldPresent {
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
},
FieldAbsent {
lhs: Cow<'script, str>,
#[serde(skip)]
key: KnownKey<'script>,
},
}
impl<'script> PredicatePattern<'script> {
fn is_exclusive_to(&self, other: &Self) -> bool {
match (self, other) {
(
PredicatePattern::Bin {
lhs: lhs1,
kind: BinOpKind::Eq,
rhs: ImutExpr::Literal(l1),
..
},
PredicatePattern::Bin {
lhs: lhs2,
kind: BinOpKind::Eq,
rhs: ImutExpr::Literal(l2),
..
},
) if lhs1 == lhs2 && !l1.ast_eq(l2) => true,
(
PredicatePattern::Bin { lhs: lhs1, .. },
PredicatePattern::FieldAbsent { lhs: lhs2, .. },
)
| (
PredicatePattern::FieldAbsent { lhs: lhs2, .. },
PredicatePattern::Bin { lhs: lhs1, .. },
) if lhs1 == lhs2 => true,
(
PredicatePattern::FieldPresent { lhs: lhs1, .. },
PredicatePattern::FieldAbsent { lhs: lhs2, .. },
)
| (
PredicatePattern::FieldAbsent { lhs: lhs2, .. },
PredicatePattern::FieldPresent { lhs: lhs1, .. },
) if lhs1 == lhs2 => true,
(
PredicatePattern::Bin {
lhs: lhs1,
kind: BinOpKind::Eq,
rhs: ImutExpr::Literal(Literal { value, .. }),
..
},
PredicatePattern::TildeEq {
lhs: lhs2, test, ..
},
)
| (
PredicatePattern::TildeEq {
lhs: lhs2, test, ..
},
PredicatePattern::Bin {
lhs: lhs1,
kind: BinOpKind::Eq,
rhs: ImutExpr::Literal(Literal { value, .. }),
..
},
) if lhs1 == lhs2 => test.extractor.is_exclusive_to(value),
(
PredicatePattern::TildeEq {
lhs: lhs1,
test: test1,
..
},
PredicatePattern::TildeEq {
lhs: lhs2,
test: test2,
..
},
) if lhs1 == lhs2 => match (test1.as_ref(), test2.as_ref()) {
(
TestExpr {
extractor: Extractor::Prefix(p1),
..
},
TestExpr {
extractor: Extractor::Prefix(p2),
..
},
) => !(p1.starts_with(p2.as_str()) || p2.starts_with(p1.as_str())),
(
TestExpr {
extractor: Extractor::Suffix(p1),
..
},
TestExpr {
extractor: Extractor::Suffix(p2),
..
},
) => !(p1.ends_with(p2.as_str()) || p2.ends_with(p1.as_str())),
_ => false,
},
(_l, _r) => false,
}
}
#[must_use]
pub(crate) fn key(&self) -> &KnownKey<'script> {
use PredicatePattern::{
ArrayPatternEq, Bin, FieldAbsent, FieldPresent, RecordPatternEq, TildeEq,
TuplePatternEq,
};
match self {
TildeEq { key, .. }
| Bin { key, .. }
| RecordPatternEq { key, .. }
| ArrayPatternEq { key, .. }
| TuplePatternEq { key, .. }
| FieldPresent { key, .. }
| FieldAbsent { key, .. } => key,
}
}
fn lhs(&self) -> &Cow<'script, str> {
use PredicatePattern::{
ArrayPatternEq, Bin, FieldAbsent, FieldPresent, RecordPatternEq, TildeEq,
TuplePatternEq,
};
match self {
TildeEq { lhs, .. }
| Bin { lhs, .. }
| RecordPatternEq { lhs, .. }
| ArrayPatternEq { lhs, .. }
| TuplePatternEq { lhs, .. }
| FieldPresent { lhs, .. }
| FieldAbsent { lhs, .. } => lhs,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct RecordPattern<'script> {
pub(crate) mid: Box<NodeMeta>,
pub fields: PatternFields<'script>,
}
impl<'script> RecordPattern<'script> {
fn is_exclusive_to(&self, other: &Self) -> bool {
if self.fields.len() == 1 && other.fields.len() == 1 {
self.fields
.first()
.and_then(|f1| Some((f1, other.fields.first()?)))
.map(|(f1, f2)| f1.is_exclusive_to(f2) || f2.is_exclusive_to(f1))
.unwrap_or_default()
} else {
false
}
}
}
impl_expr!(RecordPattern);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ArrayPredicatePattern<'script> {
Expr(ImutExpr<'script>),
Tilde(Box<TestExpr>),
Record(RecordPattern<'script>),
Ignore,
}
impl<'script> ArrayPredicatePattern<'script> {
fn is_exclusive_to(&self, other: &Self) -> bool {
match (self, other) {
(ArrayPredicatePattern::Record(r1), ArrayPredicatePattern::Record(r2)) => {
r1.is_exclusive_to(r2) || r2.is_exclusive_to(r1)
}
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ArrayPattern<'script> {
pub(crate) mid: Box<NodeMeta>,
pub exprs: ArrayPredicatePatterns<'script>,
}
impl_expr!(ArrayPattern);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct AssignPattern<'script> {
pub id: Cow<'script, str>,
pub idx: usize,
pub pattern: Box<Pattern<'script>>,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct TuplePattern<'script> {
pub(crate) mid: Box<NodeMeta>,
pub exprs: ArrayPredicatePatterns<'script>,
pub open: bool,
}
impl_expr!(TuplePattern);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum Path<'script> {
Local(LocalPath<'script>),
Event(EventPath<'script>),
State(StatePath<'script>),
Meta(MetadataPath<'script>),
Expr(ExprPath<'script>),
Reserved(ReservedPath<'script>),
}
impl<'script> Path<'script> {
#[must_use]
pub(crate) fn segments(&self) -> &Segments<'script> {
match self {
Path::Local(path) => &path.segments,
Path::Meta(path) => &path.segments,
Path::Event(path) => &path.segments,
Path::State(path) => &path.segments,
Path::Expr(path) => &path.segments,
Path::Reserved(path) => path.segments(),
}
}
#[must_use]
pub(crate) fn segments_mut(&mut self) -> &mut Segments<'script> {
match self {
Path::Local(path) => &mut path.segments,
Path::Meta(path) => &mut path.segments,
Path::Event(path) => &mut path.segments,
Path::State(path) => &mut path.segments,
Path::Expr(path) => &mut path.segments,
Path::Reserved(path) => path.segments_mut(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum Segment<'script> {
Id {
#[serde(skip)]
key: KnownKey<'script>,
mid: Box<NodeMeta>,
},
Idx {
idx: usize,
mid: Box<NodeMeta>,
},
Element {
expr: ImutExpr<'script>,
mid: Box<NodeMeta>,
},
Range {
mid: Box<NodeMeta>,
start: usize,
end: usize,
},
RangeExpr {
mid: Box<NodeMeta>,
start: Box<ImutExpr<'script>>,
end: Box<ImutExpr<'script>>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct LocalPath<'script> {
pub idx: usize,
pub(crate) mid: Box<NodeMeta>,
pub segments: Segments<'script>,
}
impl_expr!(LocalPath);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct MetadataPath<'script> {
pub(crate) mid: Box<NodeMeta>,
pub segments: Segments<'script>,
}
impl_expr!(MetadataPath);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct ExprPath<'script> {
pub(crate) expr: Box<ImutExpr<'script>>,
pub(crate) segments: Segments<'script>,
pub(crate) var: usize,
pub(crate) mid: Box<NodeMeta>,
}
impl_expr!(ExprPath);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ReservedPath<'script> {
Args {
mid: Box<NodeMeta>,
segments: Segments<'script>,
},
Window {
mid: Box<NodeMeta>,
segments: Segments<'script>,
},
Group {
mid: Box<NodeMeta>,
segments: Segments<'script>,
},
}
impl<'script> ReservedPath<'script> {
fn segments(&self) -> &Segments<'script> {
match self {
ReservedPath::Args { segments, .. }
| ReservedPath::Window { segments, .. }
| ReservedPath::Group { segments, .. } => segments,
}
}
fn segments_mut(&mut self) -> &mut Segments<'script> {
match self {
ReservedPath::Args { segments, .. }
| ReservedPath::Window { segments, .. }
| ReservedPath::Group { segments, .. } => segments,
}
}
}
impl<'script> BaseExpr for ReservedPath<'script> {
fn meta(&self) -> &NodeMeta {
match self {
ReservedPath::Args { mid, .. }
| ReservedPath::Window { mid, .. }
| ReservedPath::Group { mid, .. } => mid,
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct EventPath<'script> {
pub(crate) mid: Box<NodeMeta>,
pub segments: Segments<'script>,
}
impl_expr!(EventPath);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct StatePath<'script> {
pub(crate) mid: Box<NodeMeta>,
pub segments: Segments<'script>,
}
impl_expr!(StatePath);
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Eq)]
pub enum BinOpKind {
BitXor,
BitAnd,
Eq,
NotEq,
Gte,
Gt,
Lte,
Lt,
RBitShiftSigned,
RBitShiftUnsigned,
LBitShift,
Add,
Sub,
Mul,
Div,
Mod,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Eq)]
pub enum BooleanBinOpKind {
Or,
Xor,
And,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct BinExpr<'script> {
pub(crate) mid: Box<NodeMeta>,
pub kind: BinOpKind,
pub lhs: ImutExpr<'script>,
pub rhs: ImutExpr<'script>,
}
impl_expr!(BinExpr);
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct BooleanBinExpr<'script> {
pub(crate) mid: Box<NodeMeta>,
pub kind: BooleanBinOpKind,
pub lhs: ImutExpr<'script>,
pub rhs: ImutExpr<'script>,
}
impl_expr!(BooleanBinExpr);
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Eq)]
pub enum UnaryOpKind {
Plus,
Minus,
Not,
BitNot,
}
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct UnaryExpr<'script> {
pub(crate) mid: Box<NodeMeta>,
pub kind: UnaryOpKind,
pub expr: ImutExpr<'script>,
}
impl_expr!(UnaryExpr);
#[cfg(test)]
mod test;
pub mod optimizer;