use crate::common::ast::{
operators, CallExpr, ComprehensionExpr, Expr, IdedExpr, ListExpr, LiteralValue,
};
use crate::parser::{MacroExprHelper, ParseError};
use crate::DeclarationError;
use std::collections::BTreeMap;
use std::fmt;
use std::sync::{Arc, LazyLock};
type Expander = dyn Fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError>
+ Send
+ Sync;
#[derive(Clone)]
pub struct Macro {
function: String,
receiver_style: bool,
arity: Arity,
expander: Arc<Expander>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Arity {
Exactly(usize),
Any,
}
impl Macro {
pub fn global(
function: impl Into<String>,
arg_count: usize,
expander: impl Fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError>
+ Send
+ Sync
+ 'static,
) -> Self {
Self::new(
function,
false,
Arity::Exactly(arg_count),
Arc::new(expander),
)
}
pub fn receiver(
function: impl Into<String>,
arg_count: usize,
expander: impl Fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError>
+ Send
+ Sync
+ 'static,
) -> Self {
Self::new(
function,
true,
Arity::Exactly(arg_count),
Arc::new(expander),
)
}
pub fn global_var_arg(
function: impl Into<String>,
expander: impl Fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError>
+ Send
+ Sync
+ 'static,
) -> Self {
Self::new(function, false, Arity::Any, Arc::new(expander))
}
pub fn receiver_var_arg(
function: impl Into<String>,
expander: impl Fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError>
+ Send
+ Sync
+ 'static,
) -> Self {
Self::new(function, true, Arity::Any, Arc::new(expander))
}
fn new(
function: impl Into<String>,
receiver_style: bool,
arity: Arity,
expander: Arc<Expander>,
) -> Self {
Self {
function: function.into(),
receiver_style,
arity,
expander,
}
}
pub(crate) fn expand(
&self,
helper: &mut MacroExprHelper<'_>,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError> {
let lent = shape(target, args);
let expansion = (self.expander)(helper, target, args)?;
if expansion.is_none() && shape(target, args) != lent {
return Err(helper.new_error(
helper.id,
format!(
"macro '{}' declined the call after taking from it",
self.function
),
));
}
Ok(expansion)
}
fn matches(&self, receiver_style: bool, arity: Arity) -> bool {
self.receiver_style == receiver_style && self.arity == arity
}
}
impl fmt::Debug for Macro {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Macro")
.field("function", &self.function)
.field("receiver_style", &self.receiver_style)
.field("arity", &self.arity)
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Macros {
by_function: BTreeMap<String, Vec<Macro>>,
}
fn shape(target: &Option<IdedExpr>, args: &[IdedExpr]) -> (bool, usize, usize) {
let defaults = target
.iter()
.chain(args)
.filter(|e| e.id == 0 && matches!(e.expr, Expr::Unspecified))
.count();
(target.is_some(), args.len(), defaults)
}
type BuiltIn = fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError>;
fn expanding(
expander: BuiltIn,
) -> impl Fn(
&mut MacroExprHelper<'_>,
&mut Option<IdedExpr>,
&mut Vec<IdedExpr>,
) -> Result<Option<IdedExpr>, ParseError> {
move |helper, target, args| expander(helper, target, args).map(Some)
}
static STANDARD: LazyLock<Arc<Macros>> = LazyLock::new(|| {
Arc::new(
[
Macro::global(operators::HAS, 1, expanding(has_macro_expander)),
Macro::receiver(operators::EXISTS, 2, expanding(exists_macro_expander)),
Macro::receiver(operators::ALL, 2, expanding(all_macro_expander)),
Macro::receiver(
operators::EXISTS_ONE,
2,
expanding(exists_one_macro_expander),
),
Macro::receiver("existsOne", 2, expanding(exists_one_macro_expander)),
Macro::receiver(operators::MAP, 2, expanding(map_macro_expander)),
Macro::receiver(operators::MAP, 3, expanding(map_macro_expander)),
Macro::receiver(operators::FILTER, 2, expanding(filter_macro_expander)),
]
.into_iter()
.collect(),
)
});
impl Macros {
pub(crate) fn standard() -> Arc<Macros> {
Arc::clone(&STANDARD)
}
pub(crate) fn find(
&self,
function: &str,
target: Option<&IdedExpr>,
args: &[IdedExpr],
) -> Option<&Macro> {
let same_function = self.by_function.get(function)?;
[Arity::Exactly(args.len()), Arity::Any]
.into_iter()
.find_map(|arity| {
same_function
.iter()
.find(|m| m.matches(target.is_some(), arity))
})
}
pub(crate) fn add(&mut self, m: Macro) -> Result<(), DeclarationError> {
let same_function = self.by_function.entry(m.function.clone()).or_default();
if same_function
.iter()
.any(|other| other.matches(m.receiver_style, m.arity))
{
return Err(DeclarationError::duplicate_macro(&m.function));
}
same_function.push(m);
Ok(())
}
}
impl FromIterator<Macro> for Macros {
fn from_iter<I: IntoIterator<Item = Macro>>(macros: I) -> Self {
let mut by_function = BTreeMap::<String, Vec<Macro>>::new();
for m in macros {
by_function.entry(m.function.clone()).or_default().push(m);
}
Self { by_function }
}
}
fn has_macro_expander(
helper: &mut MacroExprHelper,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError> {
if target.is_some() {
unreachable!("Got a target when expecting `None`!")
}
if args.len() != 1 {
unreachable!("Expected a single arg!")
}
let ided_expr = args.remove(0);
match ided_expr.expr {
Expr::Select(mut select) => {
select.test = true;
Ok(helper.next_expr(Expr::Select(select)))
}
_ => Err(helper.new_error(ided_expr.id, "invalid argument to has() macro")),
}
}
fn exists_macro_expander(
helper: &mut MacroExprHelper,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError> {
if target.is_none() {
unreachable!("Expected a target, but got `None`!")
}
if args.len() != 2 {
unreachable!("Expected two args!")
}
let mut arguments = vec![args.remove(1)];
let v = extract_ident(args.remove(0), helper)?;
let init = helper.next_expr(Expr::Literal(LiteralValue::Boolean(false.into())));
let result_binding = "@result".to_string();
let accu_ident = helper.next_expr(Expr::Ident(result_binding.clone()));
let arg = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::LOGICAL_NOT.to_string(),
target: None,
args: vec![accu_ident],
}));
let condition = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::NOT_STRICTLY_FALSE.to_string(),
target: None,
args: vec![arg],
}));
arguments.insert(0, helper.next_expr(Expr::Ident(result_binding.clone())));
let step = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::LOGICAL_OR.to_string(),
target: None,
args: arguments,
}));
let result = helper.next_expr(Expr::Ident(result_binding.clone()));
Ok(
helper.next_expr(Expr::Comprehension(Box::new(ComprehensionExpr {
iter_range: target.take().unwrap(),
iter_var: v,
iter_var2: None,
accu_var: result_binding,
accu_init: init,
loop_cond: condition,
loop_step: step,
result,
}))),
)
}
fn all_macro_expander(
helper: &mut MacroExprHelper,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError> {
if target.is_none() {
unreachable!("Expected a target, but got `None`!")
}
if args.len() != 2 {
unreachable!("Expected two args!")
}
let mut arguments = vec![args.remove(1)];
let v = extract_ident(args.remove(0), helper)?;
let init = helper.next_expr(Expr::Literal(LiteralValue::Boolean(true.into())));
let result_binding = "@result".to_string();
let accu_ident = helper.next_expr(Expr::Ident(result_binding.clone()));
let condition = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::NOT_STRICTLY_FALSE.to_string(),
target: None,
args: vec![accu_ident],
}));
arguments.insert(0, helper.next_expr(Expr::Ident(result_binding.clone())));
let step = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::LOGICAL_AND.to_string(),
target: None,
args: arguments,
}));
let result = helper.next_expr(Expr::Ident(result_binding.clone()));
Ok(
helper.next_expr(Expr::Comprehension(Box::new(ComprehensionExpr {
iter_range: target.take().unwrap(),
iter_var: v,
iter_var2: None,
accu_var: result_binding,
accu_init: init,
loop_cond: condition,
loop_step: step,
result,
}))),
)
}
fn exists_one_macro_expander(
helper: &mut MacroExprHelper,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError> {
if target.is_none() {
unreachable!("Expected a target, but got `None`!")
}
if args.len() != 2 {
unreachable!("Expected two args!")
}
let mut arguments = vec![args.remove(1)];
let v = extract_ident(args.remove(0), helper)?;
let init = helper.next_expr(Expr::Literal(LiteralValue::Int(0.into())));
let result_binding = "@result".to_string();
let condition = helper.next_expr(Expr::Literal(LiteralValue::Boolean(true.into())));
let args = vec![
helper.next_expr(Expr::Ident(result_binding.clone())),
helper.next_expr(Expr::Literal(LiteralValue::Int(1.into()))),
];
arguments.push(helper.next_expr(Expr::Call(CallExpr {
func_name: operators::ADD.to_string(),
target: None,
args,
})));
arguments.push(helper.next_expr(Expr::Ident(result_binding.clone())));
let step = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::CONDITIONAL.to_string(),
target: None,
args: arguments,
}));
let accu = helper.next_expr(Expr::Ident(result_binding.clone()));
let one = helper.next_expr(Expr::Literal(LiteralValue::Int(1.into())));
let result = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::EQUALS.to_string(),
target: None,
args: vec![accu, one],
}));
Ok(
helper.next_expr(Expr::Comprehension(Box::new(ComprehensionExpr {
iter_range: target.take().unwrap(),
iter_var: v,
iter_var2: None,
accu_var: result_binding,
accu_init: init,
loop_cond: condition,
loop_step: step,
result,
}))),
)
}
pub(crate) fn map_macro_expander(
helper: &mut MacroExprHelper,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError> {
if target.is_none() {
unreachable!("Expected a target, but got `None`!")
}
if args.len() != 2 && args.len() != 3 {
unreachable!("Expected two or three args!")
}
let func = args.pop().unwrap();
let v = extract_ident(args.remove(0), helper)?;
let init = helper.next_expr(Expr::List(ListExpr::new(Vec::default())));
let result_binding = "@result".to_string();
let condition = helper.next_expr(Expr::Literal(LiteralValue::Boolean(true.into())));
let filter = args.pop();
let args = vec![
helper.next_expr(Expr::Ident(result_binding.clone())),
helper.next_expr(Expr::List(ListExpr::new(vec![func]))),
];
let step = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::ADD.to_string(),
target: None,
args,
}));
let step = match filter {
Some(filter) => {
let accu = helper.next_expr(Expr::Ident(result_binding.clone()));
helper.next_expr(Expr::Call(CallExpr {
func_name: operators::CONDITIONAL.to_string(),
target: None,
args: vec![filter, step, accu],
}))
}
None => step,
};
let result = helper.next_expr(Expr::Ident(result_binding.clone()));
Ok(
helper.next_expr(Expr::Comprehension(Box::new(ComprehensionExpr {
iter_range: target.take().unwrap(),
iter_var: v,
iter_var2: None,
accu_var: result_binding,
accu_init: init,
loop_cond: condition,
loop_step: step,
result,
}))),
)
}
fn filter_macro_expander(
helper: &mut MacroExprHelper,
target: &mut Option<IdedExpr>,
args: &mut Vec<IdedExpr>,
) -> Result<IdedExpr, ParseError> {
if target.is_none() {
unreachable!("Expected a target, but got `None`!")
}
if args.len() != 2 {
unreachable!("Expected two args!")
}
let var = args.remove(0);
let v = extract_ident(var.clone(), helper)?;
let filter = args.pop().unwrap();
let init = helper.next_expr(Expr::List(ListExpr::new(Vec::default())));
let result_binding = "@result".to_string();
let condition = helper.next_expr(Expr::Literal(LiteralValue::Boolean(true.into())));
let args = vec![
helper.next_expr(Expr::Ident(result_binding.clone())),
helper.next_expr(Expr::List(ListExpr::new(vec![var]))),
];
let step = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::ADD.to_string(),
target: None,
args,
}));
let accu = helper.next_expr(Expr::Ident(result_binding.clone()));
let step = helper.next_expr(Expr::Call(CallExpr {
func_name: operators::CONDITIONAL.to_string(),
target: None,
args: vec![filter, step, accu],
}));
let result = helper.next_expr(Expr::Ident(result_binding.clone()));
Ok(
helper.next_expr(Expr::Comprehension(Box::new(ComprehensionExpr {
iter_range: target.take().unwrap(),
iter_var: v,
iter_var2: None,
accu_var: result_binding,
accu_init: init,
loop_cond: condition,
loop_step: step,
result,
}))),
)
}
fn extract_ident(expr: IdedExpr, helper: &mut MacroExprHelper) -> Result<String, ParseError> {
match expr.expr {
Expr::Ident(ident) => Ok(ident),
_ => Err(helper.new_error(expr.id, "argument must be a simple name")),
}
}