use crate::{eval::*, lower::ir};
use microcad_lang_base::{Identifier, SrcReferrer};
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum Priority {
Empty,
Id,
Short,
Type,
TypeAuto,
Default,
None,
}
impl Priority {
pub(super) fn high_to_low() -> &'static [Priority] {
&[
Self::Empty,
Self::Id,
Self::Short,
Self::Type,
Self::TypeAuto,
Self::Default,
]
}
fn set_once(&mut self, with: Self) {
if *self == Priority::None {
*self = with
}
}
}
impl std::fmt::Display for Priority {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "<NONE>"),
Self::Default => write!(f, "Default"),
Self::TypeAuto => write!(f, "TypeAuto"),
Self::Type => write!(f, "Type"),
Self::Short => write!(f, "Short"),
Self::Id => write!(f, "Id"),
Self::Empty => write!(f, "Empty"),
}
}
}
pub struct ArgumentMatch<'a> {
arguments: Vec<(&'a Identifier, &'a ArgumentValue)>,
params: Vec<(&'a Identifier, &'a ParameterValue)>,
result: Tuple,
priority: Priority,
}
#[derive(Debug)]
pub struct MultiMatchResult {
pub args: Vec<Tuple>,
pub priority: Priority,
}
impl<'a> ArgumentMatch<'a> {
pub fn find_match(
arguments: &'a ArgumentValueList,
params: &'a ParameterValueList,
) -> EvalResult<Tuple> {
let am = Self::new(arguments, params)?;
am.check_exact_types(params)?;
Ok(am.result)
}
pub fn find_multi_match(
arguments: &'a ArgumentValueList,
params: &'a ParameterValueList,
) -> EvalResult<MultiMatchResult> {
let m = Self::new(arguments, params)?;
Ok(MultiMatchResult {
args: m.multiply(params),
priority: m.priority,
})
}
fn new(arguments: &'a ArgumentValueList, params: &'a ParameterValueList) -> EvalResult<Self> {
let mut am = Self {
arguments: arguments.iter().map(|(id, v)| (id, v)).collect(),
params: params.iter().collect(),
result: Tuple::new_named(microcad_core::hash::HashMap::default(), arguments.src_ref()),
priority: Priority::None,
};
log::trace!("matching arguments:\n{am:?}");
if !am.match_empty(Priority::Empty) {
am.match_ids(Priority::Id, |l, r| l == r)?;
am.match_ids(Priority::Short, |l, r| &l.short_id() == r)?;
am.match_types(Priority::Type, |l, r| l == r, true);
am.match_types(Priority::TypeAuto, |l, r| l.is_matching(r), false);
am.match_defaults(Priority::Default);
am.match_types(Priority::TypeAuto, |l, r| l.is_matching(r), false);
am.check_missing()?;
}
Ok(am)
}
fn match_empty(&mut self, priority: Priority) -> bool {
if self.arguments.is_empty() && self.params.is_empty() {
self.priority.set_once(priority);
true
} else {
false
}
}
fn match_ids(
&mut self,
priority: Priority,
match_fn: impl Fn(&Identifier, &Identifier) -> bool,
) -> EvalResult<()> {
let mut type_mismatch = Vec::new();
if self.arguments.is_empty() {
return Ok(());
}
self.arguments.retain(|(id, arg)| {
let id = match (id.is_empty(), &arg.inline_id) {
(true, Some(id)) => id,
_ => id,
};
if id.is_empty() {
return true;
}
match self.params.iter().position(|(i, _)| match_fn(i, id)) {
None => true,
Some(n) => {
if let Some(ty) = &self.params[n].1.specified_type {
if !arg.ty().is_matching(ty) {
type_mismatch.push((id.clone(), arg.ty(), ty));
return true;
}
}
let (id, _) = self.params.swap_remove(n);
log::trace!(
"{found} parameter by id: {id:?}",
found = microcad_lang_base::mark!(MATCH)
);
self.priority.set_once(priority);
self.result.insert((*id).clone(), arg.value.clone());
false
}
}
});
if type_mismatch.is_empty() {
Ok(())
} else {
let type_mismatch = type_mismatch
.iter()
.map(|(id, ty1, ty2)| format!("{id}: {ty1} != {ty2}"))
.collect::<Vec<_>>()
.join(", ");
Err(EvalError::IdMatchButNotType(type_mismatch).into())
}
}
fn match_types(
&mut self,
priority: Priority,
match_fn: impl Fn(&Type, &Type) -> bool,
mut exclude_defaults: bool,
) {
if self.arguments.is_empty() {
return;
}
self.arguments.retain(|(arg_id, arg)| {
let same_type: Vec<_> = self
.params
.iter()
.enumerate()
.filter(|(..)| arg_id.is_empty())
.filter_map(|(n, (id, param))| {
if param.ty() == Type::Invalid
|| if let Some(ty) = ¶m.specified_type {
match_fn(&arg.ty(), ty)
} else {
false
}
{
Some((n, *id, *param))
} else {
None
}
})
.collect();
if same_type.len() == 1 {
exclude_defaults = false;
}
let mut same_type = same_type
.into_iter()
.filter(|(.., param)| !exclude_defaults || param.default_value.is_none());
if let Some((n, id, _)) = same_type.next() {
if same_type.next().is_none() {
log::trace!(
"{found} parameter by type: {id:?}",
found = microcad_lang_base::mark!(MATCH)
);
self.priority.set_once(priority);
self.result.insert(id.clone(), arg.value.clone());
self.params.swap_remove(n);
return false;
} else {
log::trace!("more than one parameter with that type")
}
} else {
log::trace!("no parameter with that type (or id mismatch)")
}
true
})
}
fn match_defaults(&mut self, priority: Priority) {
if self.params.is_empty() {
return;
}
self.params.retain(|(id, param)| {
if let Some(def) = ¶m.default_value {
if def.ty() == param.ty() {
log::trace!(
"{found} argument by default: {id:?} = {def}",
found = microcad_lang_base::mark!(MATCH)
);
self.priority.set_once(priority);
self.result.insert((*id).clone(), def.clone());
return false;
}
}
true
})
}
fn check_missing(&self) -> EvalResult<()> {
if !self.params.is_empty() {
let mut missing: ir::IdentifierList =
self.params.iter().map(|(id, _)| (*id).clone()).collect();
missing.sort();
Err(EvalError::MissingArguments(missing).into())
} else if !self.arguments.is_empty() {
let mut too_many: ir::IdentifierList =
self.arguments.iter().map(|(id, _)| (*id).clone()).collect();
too_many.sort();
Err(EvalError::TooManyArguments(too_many).into())
} else {
Ok(())
}
}
fn check_exact_types(&self, params: &ParameterValueList) -> EvalResult<()> {
let multipliers = Self::multipliers(&self.result, params);
if multipliers.is_empty() {
return Ok(());
}
Err(EvalError::MultiplicityNotAllowed(multipliers).into())
}
fn multiply(&self, params: &ParameterValueList) -> Vec<Tuple> {
let ids: ir::IdentifierList = Self::multipliers(&self.result, params);
if !ids.is_empty() {
let mut result = Vec::new();
self.result.multiplicity(ids, |t| result.push(t));
result
} else {
vec![self.result.clone()]
}
}
fn multipliers(args: &impl ValueAccess, params: &ParameterValueList) -> ir::IdentifierList {
let mut result: ir::IdentifierList = params
.iter()
.filter_map(|(id, param)| {
if let Some(a) = args.by_id(id) {
if a.ty().is_array_of(¶m.ty()) {
return Some(id);
}
}
None
})
.cloned()
.collect();
result.sort();
result
}
}
impl std::fmt::Debug for ArgumentMatch<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
" Arguments: {args}\n Parameters: {params}",
args = self
.arguments
.iter()
.map(|(id, arg)| format!(
"{id:?} : {val:?}",
id = match (id.is_empty(), &arg.inline_id) {
(_, None) => id,
(true, Some(inline_id)) => inline_id,
(false, Some(_)) => id,
},
val = arg.value
))
.collect::<Vec<_>>()
.join(", "),
params = self
.params
.iter()
.map(|(id, param)| format!("{id:?} {param:?}"))
.collect::<Vec<_>>()
.join(", "),
)
}
}
#[test]
fn argument_matching() {
let _ = env_logger::try_init();
use microcad_core::Length;
let params: ParameterValueList = [
crate::parameter!(a: Scalar),
crate::parameter!(b: Length),
crate::parameter!(c: Scalar),
crate::parameter!(d: Length = Length::mm(4.0)),
]
.into_iter()
.collect();
let arguments: ArgumentValueList = [
crate::argument!(a: Scalar = 1.0),
crate::argument!(b: Length = Length::mm(2.0)),
crate::argument!(Scalar = 3.0),
]
.into_iter()
.collect();
let result = ArgumentMatch::find_match(&arguments, ¶ms).expect("expect valid arguments");
assert_eq!(
result,
crate::create_tuple!(a = 1.0, b = Length::mm(2.0), c = 3.0, d = Length::mm(4.0))
);
}
#[test]
fn argument_match_fail() {
use microcad_core::Length;
let params: ParameterValueList = [
crate::parameter!(x: Scalar),
crate::parameter!(y: Length),
crate::parameter!(z: Area),
]
.into_iter()
.collect();
let arguments: ArgumentValueList = [
crate::argument!(x: Scalar = 1.0),
crate::argument!(Length = Length::mm(1.0)),
]
.into_iter()
.collect();
assert!(ArgumentMatch::find_match(&arguments, ¶ms).is_err());
}