use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{
fmt_doc_comments,
module::options::{MarkdownProcessor, SectionFormat, RHAI_FUNCTION_INDEX_PATTERN},
remove_test_code,
};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub(crate) struct FunctionMetadata {
pub access: String,
pub base_hash: u128,
pub full_hash: u128,
pub name: String,
pub namespace: String,
pub num_params: usize,
pub params: Option<Vec<std::collections::HashMap<String, String>>>,
pub signature: String,
pub return_type: Option<String>,
pub doc_comments: Option<Vec<String>>,
}
fn remove_extra_tokens(dc: Vec<String>) -> Vec<String> {
dc.into_iter()
.map(|s| {
s.lines()
.filter(|l| !l.contains(RHAI_FUNCTION_INDEX_PATTERN))
.collect::<Vec<_>>()
.join("\n")
})
.collect::<Vec<_>>()
}
impl FunctionMetadata {
pub fn fmt_doc_comments(
&self,
section_format: &SectionFormat,
markdown_processor: &MarkdownProcessor,
) -> Option<String> {
self.doc_comments.clone().map(|dc| {
let removed_extra_tokens = remove_extra_tokens(dc).join("\n");
let remove_comments = fmt_doc_comments(removed_extra_tokens);
let remove_test_code = remove_test_code(&remove_comments);
section_format.fmt_sections(&self.name, markdown_processor, remove_test_code)
})
}
pub fn generate_function_definition(&self) -> Definition {
Definition::new(
&self.name,
self.params.as_ref().unwrap_or(&vec![]),
self.return_type.clone(),
)
}
}
fn is_operator(name: &str) -> bool {
["==", "!=", ">", ">=", "<", "<=", "in"]
.into_iter()
.any(|op| op == name)
}
fn def_type_name(ty: &str) -> Option<String> {
let ty = ty.strip_prefix("&mut").unwrap_or(ty).trim();
let ty = remove_result(ty);
let ty = ty.split("::").last().unwrap();
let ty = ty
.replace("Iterator<Item=", "Iterator<")
.replace("Dynamic", "?")
.replace("INT", "int")
.replace(std::any::type_name::<rhai::INT>(), "int")
.replace("FLOAT", "float")
.replace("&str", "String")
.replace("ImmutableString", "String");
let ty = ty.replace(std::any::type_name::<rhai::FLOAT>(), "float");
let ty = ty.replace(std::any::type_name::<rhai::Array>(), "Array");
let ty = ty.replace(std::any::type_name::<rhai::Blob>(), "Blob");
let ty = ty.replace(std::any::type_name::<rhai::Map>(), "Map");
let ty = ty.replace(std::any::type_name::<rhai::Instant>(), "Instant");
let ty = ty.replace(std::any::type_name::<rhai::FnPtr>(), "FnPtr");
if ty == "()" {
None
} else {
Some(ty)
}
}
fn remove_result(ty: &str) -> &str {
if let Some(ty) = ty.strip_prefix("Result<") {
ty.strip_suffix(",Box<EvalAltResult>>")
.or_else(|| ty.strip_suffix(",Box<rhai::EvalAltResult>>"))
.or_else(|| ty.strip_suffix(", Box<EvalAltResult>>"))
.or_else(|| ty.strip_suffix(", Box<rhai::EvalAltResult>>"))
.or_else(|| ty.strip_suffix('>'))
} else if let Some(ty) = ty.strip_prefix("EngineResult<") {
ty.strip_suffix('>')
} else if let Some(ty) = ty
.strip_prefix("RhaiResultOf<")
.or_else(|| ty.strip_prefix("rhai::RhaiResultOf<"))
{
ty.strip_suffix('>')
} else {
None
}
.map_or(ty, str::trim)
}
pub struct Arg {
name: String,
ty: String,
}
impl Arg {
fn unknown() -> Self {
Self {
name: "_".to_string(),
ty: "?".into(),
}
}
}
impl Display for Arg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}: {}", self.name, self.ty)
}
}
pub enum Definition {
Function {
name: String,
args: Vec<Arg>,
return_type: Option<String>,
},
Operator {
name: String,
arg1: Arg,
arg2: Arg,
return_type: Option<String>,
},
Get {
target: Arg,
index: Arg,
return_type: Option<String>,
},
Set {
target: Arg,
index: Arg,
value: Arg,
},
IndexGet {
target: Arg,
index: Arg,
return_type: Option<String>,
},
IndexSet {
target: Arg,
index: Arg,
value: Arg,
},
}
impl Definition {
pub fn new(
name: &str,
args: &[std::collections::HashMap<String, String>],
return_type: Option<String>,
) -> Self {
fn get_arg(args: &[std::collections::HashMap<String, String>], index: usize) -> Arg {
args.get(index).map_or(Arg::unknown(), |def| Arg {
name: def
.get("name")
.map(|n| n.as_str())
.unwrap_or("_")
.to_string(),
ty: def
.get("type")
.and_then(|ty| def_type_name(ty))
.unwrap_or("?".to_string())
.to_string(),
})
}
let return_type = return_type.as_deref().and_then(def_type_name);
if is_operator(name) {
Self::Operator {
name: name.to_string(),
arg1: get_arg(args, 0),
arg2: get_arg(args, 1),
return_type,
}
} else if let Some(name) = name.strip_prefix("get$") {
Self::Get {
target: get_arg(args, 0),
index: Arg {
name: name.to_string(),
ty: "_".to_string(),
},
return_type,
}
} else if let Some(name) = name.strip_prefix("set$") {
Self::Set {
target: get_arg(args, 0),
index: Arg {
name: name.to_string(),
ty: "_".to_string(),
},
value: get_arg(args, 1),
}
} else if name.strip_prefix("index$get$").is_some() {
Self::IndexGet {
target: get_arg(args, 0),
index: get_arg(args, 1),
return_type,
}
} else if name.strip_prefix("index$set$").is_some() {
Self::IndexSet {
target: get_arg(args, 0),
index: get_arg(args, 1),
value: get_arg(args, 2),
}
} else {
Self::Function {
name: name.to_string(),
args: args
.iter()
.enumerate()
.map(|(index, _)| get_arg(args, index))
.collect::<Vec<Arg>>(),
return_type,
}
}
}
pub fn display(&self) -> String {
match self {
Self::Function {
name,
args,
return_type,
} => {
format!(
"fn {}({})",
name,
args.iter()
.map(|arg| arg.to_string())
.collect::<Vec<String>>()
.join(", ")
) + return_type
.as_ref()
.map_or(String::default(), |rt| format!(" -> {rt}"))
.as_str()
}
Self::Operator {
name,
arg1,
arg2,
return_type,
} => {
format!("op {} {} {}", arg1.ty, name, arg2.ty)
+ return_type
.as_ref()
.map_or(")".to_string(), |rt| format!(" -> {rt}"))
.as_str()
}
Self::Get {
target,
index,
return_type,
} => {
format!("get {}.{}", target.ty, index.name)
+ return_type
.as_ref()
.map_or(")".to_string(), |rt| format!(" -> {rt}"))
.as_str()
}
Self::Set {
target,
index,
value,
} => {
format!("set {}.{} = {}", target.ty, index.name, value.ty)
}
Self::IndexGet {
target,
index,
return_type,
} => {
format!("index get {}[{}]", target.ty, index)
+ return_type
.as_ref()
.map_or(")".to_string(), |rt| format!(" -> {rt}"))
.as_str()
}
Self::IndexSet {
target,
index,
value,
} => format!("index set {}[{}] = {}", target.ty, index, value.ty),
}
}
pub fn type_to_str(&self) -> &'static str {
match self {
Self::Function { .. } => "fn",
Self::Operator { .. } => "op",
Self::Get { .. } => "get",
Self::Set { .. } => "set",
Self::IndexGet { .. } => "index get",
Self::IndexSet { .. } => "index set",
}
}
pub fn name(&self) -> &str {
match self {
Self::Function { name, .. } | Self::Operator { name, .. } => name,
Self::Get { index, .. }
| Self::Set { index, .. }
| Self::IndexGet { index, .. }
| Self::IndexSet { index, .. } => &index.name,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_remove_result() {
assert_eq!("Cache", remove_result("Result<Cache, Box<EvalAltResult>>"));
assert_eq!("Cache", remove_result("Result<Cache,Box<EvalAltResult>>"));
assert_eq!(
"&mut Cache",
remove_result("Result<&mut Cache, Box<EvalAltResult>>")
);
assert_eq!(
"Cache",
remove_result("Result<Cache, Box<rhai::EvalAltResult>>")
);
assert_eq!(
"Cache",
remove_result("Result<Cache,Box<rhai::EvalAltResult>>")
);
assert_eq!("Stuff", remove_result("EngineResult<Stuff>"));
assert_eq!("Stuff", remove_result("RhaiResultOf<Stuff>"));
assert_eq!("Stuff", remove_result("rhai::RhaiResultOf<Stuff>"));
}
}