use crate::{CodePattern, NameMatcher, NodeKind, PatternExpr};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ParseError {
#[error("Failed to parse pattern: {0}")]
SynError(#[from] syn::Error),
#[error("Unsupported syntax: {0}")]
UnsupportedSyntax(String),
#[error("Invalid metavariable: {0}")]
InvalidMetavariable(String),
}
pub type ParseResult<T> = Result<T, ParseError>;
pub fn parse_pattern(input: &str) -> ParseResult<CodePattern> {
let parser = ConcreteParser::new();
parser.parse(input)
}
const METAVAR_PREFIX: &str = "__mv_";
const ELLIPSIS_PREFIX: &str = "__ell_";
pub struct ConcreteParser {
}
impl ConcreteParser {
pub fn new() -> Self {
Self {}
}
pub fn parse(&self, input: &str) -> ParseResult<CodePattern> {
let preprocessed = self.preprocess(input);
if let Ok(expr) = syn::parse_str::<syn::Expr>(&preprocessed) {
return self.convert_expr(&expr);
}
if let Ok(stmt) = syn::parse_str::<syn::Stmt>(&preprocessed) {
return self.convert_stmt(&stmt);
}
if let Ok(item) = syn::parse_str::<syn::Item>(&preprocessed) {
return self.convert_item(&item);
}
Err(ParseError::UnsupportedSyntax(format!(
"Could not parse as expression, statement, or item: {}",
input
)))
}
fn preprocess(&self, input: &str) -> String {
let mut result = String::with_capacity(input.len() * 2);
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c == '$' {
let mut is_ellipsis = false;
let mut dots = String::new();
while chars.peek() == Some(&'.') {
dots.push(chars.next().unwrap());
}
if dots.len() >= 3 {
is_ellipsis = true;
} else {
}
let mut var_name = String::new();
while let Some(&ch) = chars.peek() {
if ch.is_alphanumeric() || ch == '_' {
var_name.push(chars.next().unwrap());
} else {
break;
}
}
if is_ellipsis {
result.push_str(ELLIPSIS_PREFIX);
result.push_str(&var_name);
} else if !var_name.is_empty() {
result.push_str(METAVAR_PREFIX);
result.push_str(&dots); result.push_str(&var_name);
} else {
result.push('$');
result.push_str(&dots);
}
} else {
result.push(c);
}
}
result
}
fn convert_expr(&self, expr: &syn::Expr) -> ParseResult<CodePattern> {
match expr {
syn::Expr::MethodCall(mc) => self.convert_method_call(mc),
syn::Expr::Call(call) => self.convert_call(call),
syn::Expr::Macro(mac) => self.convert_macro(mac),
syn::Expr::Path(path) => self.convert_path(path),
syn::Expr::If(if_expr) => self.convert_if(if_expr),
syn::Expr::Match(match_expr) => self.convert_match(match_expr),
syn::Expr::Block(block) => self.convert_block(block),
syn::Expr::Binary(bin) => self.convert_binary(bin),
syn::Expr::Unary(unary) => self.convert_unary(unary),
syn::Expr::Lit(lit) => self.convert_literal(lit),
syn::Expr::Try(try_expr) => self.convert_try(try_expr),
syn::Expr::Return(ret) => self.convert_return(ret),
syn::Expr::Await(await_expr) => self.convert_await(await_expr),
syn::Expr::Closure(closure) => self.convert_closure(closure),
_ => Err(ParseError::UnsupportedSyntax(
"Unsupported expression type".to_string(),
)),
}
}
fn convert_method_call(&self, mc: &syn::ExprMethodCall) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::MethodCall);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&mc.receiver) {
children.insert(
"receiver".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let receiver_pattern = self.convert_expr(&mc.receiver)?;
children.insert(
"receiver".to_string(),
PatternExpr::Pattern(Box::new(receiver_pattern)),
);
}
let method_name = mc.method.to_string();
if is_metavariable(&method_name) {
children.insert(
"method".to_string(),
PatternExpr::Capture(to_metavar_name(&method_name)),
);
} else {
children.insert(
"method".to_string(),
PatternExpr::Name(NameMatcher::Exact(method_name)),
);
}
if !mc.args.is_empty() {
let args_pattern = self.convert_args(&mc.args)?;
children.insert("args".to_string(), args_pattern);
}
pattern.children = children;
Ok(pattern)
}
fn convert_call(&self, call: &syn::ExprCall) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::FunctionCall);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&call.func) {
children.insert(
"func".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let func_pattern = self.convert_expr(&call.func)?;
children.insert(
"func".to_string(),
PatternExpr::Pattern(Box::new(func_pattern)),
);
}
if !call.args.is_empty() {
let args_pattern = self.convert_args(&call.args)?;
children.insert("args".to_string(), args_pattern);
}
pattern.children = children;
Ok(pattern)
}
fn convert_macro(&self, mac: &syn::ExprMacro) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::MacroCall);
let mut children = HashMap::new();
let macro_name = mac
.mac
.path
.segments
.last()
.map(|s| s.ident.to_string())
.unwrap_or_default();
children.insert(
"name".to_string(),
PatternExpr::Name(NameMatcher::Exact(macro_name)),
);
let tokens_str = mac.mac.tokens.to_string();
if let Some(ellipsis_var) = extract_ellipsis_var(&tokens_str) {
pattern.ellipsis = true;
pattern.capture = Some(ellipsis_var);
}
pattern.children = children;
Ok(pattern)
}
fn convert_path(&self, path: &syn::ExprPath) -> ParseResult<CodePattern> {
let path_str = path_to_string(&path.path);
if is_metavariable(&path_str) {
let mut pattern = CodePattern::new(NodeKind::Expr);
pattern.capture = Some(to_metavar_name(&path_str));
Ok(pattern)
} else {
let mut pattern = CodePattern::new(NodeKind::Path);
pattern.children.insert(
"path".to_string(),
PatternExpr::Name(NameMatcher::Exact(path_str)),
);
Ok(pattern)
}
}
fn convert_if(&self, if_expr: &syn::ExprIf) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::If);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&if_expr.cond) {
children.insert(
"condition".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let cond_pattern = self.convert_expr(&if_expr.cond)?;
children.insert(
"condition".to_string(),
PatternExpr::Pattern(Box::new(cond_pattern)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_match(&self, match_expr: &syn::ExprMatch) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Match);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&match_expr.expr) {
children.insert(
"scrutinee".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let scrutinee_pattern = self.convert_expr(&match_expr.expr)?;
children.insert(
"scrutinee".to_string(),
PatternExpr::Pattern(Box::new(scrutinee_pattern)),
);
}
children.insert(
"arms_count".to_string(),
PatternExpr::Literal(serde_json::json!(match_expr.arms.len())),
);
pattern.children = children;
Ok(pattern)
}
fn convert_block(&self, _block: &syn::ExprBlock) -> ParseResult<CodePattern> {
let pattern = CodePattern::new(NodeKind::Block);
Ok(pattern)
}
fn convert_binary(&self, bin: &syn::ExprBinary) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::BinaryOp);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&bin.left) {
children.insert(
"left".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let left_pattern = self.convert_expr(&bin.left)?;
children.insert(
"left".to_string(),
PatternExpr::Pattern(Box::new(left_pattern)),
);
}
let op_str = binop_to_string(&bin.op);
children.insert(
"op".to_string(),
PatternExpr::Literal(serde_json::json!(op_str)),
);
if let Some(capture) = self.extract_metavar_from_expr(&bin.right) {
children.insert(
"right".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let right_pattern = self.convert_expr(&bin.right)?;
children.insert(
"right".to_string(),
PatternExpr::Pattern(Box::new(right_pattern)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_unary(&self, unary: &syn::ExprUnary) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::UnaryOp);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&unary.expr) {
children.insert(
"operand".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let operand_pattern = self.convert_expr(&unary.expr)?;
children.insert(
"operand".to_string(),
PatternExpr::Pattern(Box::new(operand_pattern)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_literal(&self, lit: &syn::ExprLit) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Literal);
let value = match &lit.lit {
syn::Lit::Str(s) => serde_json::json!(s.value()),
syn::Lit::Int(i) => serde_json::json!(i.base10_parse::<i64>().unwrap_or(0)),
syn::Lit::Float(f) => serde_json::json!(f.base10_parse::<f64>().unwrap_or(0.0)),
syn::Lit::Bool(b) => serde_json::json!(b.value()),
syn::Lit::Char(c) => serde_json::json!(c.value().to_string()),
_ => serde_json::json!(null),
};
pattern
.children
.insert("value".to_string(), PatternExpr::Literal(value));
Ok(pattern)
}
fn convert_try(&self, try_expr: &syn::ExprTry) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Try);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&try_expr.expr) {
children.insert(
"expr".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let expr_pattern = self.convert_expr(&try_expr.expr)?;
children.insert(
"expr".to_string(),
PatternExpr::Pattern(Box::new(expr_pattern)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_return(&self, ret: &syn::ExprReturn) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Return);
if let Some(expr) = &ret.expr {
if let Some(capture) = self.extract_metavar_from_expr(expr) {
pattern.children.insert(
"value".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let expr_pattern = self.convert_expr(expr)?;
pattern.children.insert(
"value".to_string(),
PatternExpr::Pattern(Box::new(expr_pattern)),
);
}
}
Ok(pattern)
}
fn convert_await(&self, await_expr: &syn::ExprAwait) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Await);
let mut children = HashMap::new();
if let Some(capture) = self.extract_metavar_from_expr(&await_expr.base) {
children.insert(
"base".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let base_pattern = self.convert_expr(&await_expr.base)?;
children.insert(
"base".to_string(),
PatternExpr::Pattern(Box::new(base_pattern)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_closure(&self, _closure: &syn::ExprClosure) -> ParseResult<CodePattern> {
let pattern = CodePattern::new(NodeKind::Closure);
Ok(pattern)
}
fn convert_stmt(&self, stmt: &syn::Stmt) -> ParseResult<CodePattern> {
match stmt {
syn::Stmt::Expr(expr, _) => self.convert_expr(expr),
syn::Stmt::Local(local) => self.convert_local(local),
_ => Err(ParseError::UnsupportedSyntax(
"Unsupported statement type".to_string(),
)),
}
}
fn convert_local(&self, local: &syn::Local) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::LetExpr);
if let syn::Pat::Ident(ident) = &local.pat {
let name = ident.ident.to_string();
if is_metavariable(&name) {
pattern.children.insert(
"pattern".to_string(),
PatternExpr::Capture(to_metavar_name(&name)),
);
}
}
if let Some(init) = &local.init {
if let Some(capture) = self.extract_metavar_from_expr(&init.expr) {
pattern.children.insert(
"init".to_string(),
PatternExpr::Capture(to_metavar_name(&capture)),
);
} else {
let init_pattern = self.convert_expr(&init.expr)?;
pattern.children.insert(
"init".to_string(),
PatternExpr::Pattern(Box::new(init_pattern)),
);
}
}
Ok(pattern)
}
fn convert_item(&self, item: &syn::Item) -> ParseResult<CodePattern> {
match item {
syn::Item::Fn(func) => self.convert_fn(func),
syn::Item::Struct(s) => self.convert_struct(s),
_ => Err(ParseError::UnsupportedSyntax(
"Unsupported item type".to_string(),
)),
}
}
fn convert_fn(&self, func: &syn::ItemFn) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Function);
let mut children = HashMap::new();
let name = func.sig.ident.to_string();
if is_metavariable(&name) {
children.insert(
"name".to_string(),
PatternExpr::Capture(to_metavar_name(&name)),
);
} else {
children.insert(
"name".to_string(),
PatternExpr::Name(NameMatcher::Exact(name)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_struct(&self, s: &syn::ItemStruct) -> ParseResult<CodePattern> {
let mut pattern = CodePattern::new(NodeKind::Struct);
let mut children = HashMap::new();
let name = s.ident.to_string();
if is_metavariable(&name) {
children.insert(
"name".to_string(),
PatternExpr::Capture(to_metavar_name(&name)),
);
} else {
children.insert(
"name".to_string(),
PatternExpr::Name(NameMatcher::Exact(name)),
);
}
pattern.children = children;
Ok(pattern)
}
fn convert_args(
&self,
args: &syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>,
) -> ParseResult<PatternExpr> {
for arg in args {
if let Some(ellipsis_var) = self.extract_ellipsis_from_expr(arg) {
return Ok(PatternExpr::Capture(to_metavar_name(&ellipsis_var)));
}
}
let mut arg_patterns = Vec::new();
for arg in args {
if let Some(capture) = self.extract_metavar_from_expr(arg) {
arg_patterns.push(serde_json::json!({ "capture": to_metavar_name(&capture) }));
} else {
let pattern = self.convert_expr(arg)?;
arg_patterns.push(serde_json::to_value(&pattern).unwrap_or_default());
}
}
Ok(PatternExpr::Literal(serde_json::json!(arg_patterns)))
}
fn extract_metavar_from_expr(&self, expr: &syn::Expr) -> Option<String> {
if let syn::Expr::Path(path) = expr {
let path_str = path_to_string(&path.path);
if is_metavariable(&path_str) {
return Some(path_str);
}
}
None
}
fn extract_ellipsis_from_expr(&self, expr: &syn::Expr) -> Option<String> {
if let syn::Expr::Path(path) = expr {
let path_str = path_to_string(&path.path);
if is_ellipsis_metavariable(&path_str) {
return Some(path_str);
}
}
None
}
}
impl Default for ConcreteParser {
fn default() -> Self {
Self::new()
}
}
fn is_metavariable(s: &str) -> bool {
s.starts_with(METAVAR_PREFIX) || s.starts_with(ELLIPSIS_PREFIX)
}
fn is_ellipsis_metavariable(s: &str) -> bool {
s.starts_with(ELLIPSIS_PREFIX)
}
fn to_metavar_name(s: &str) -> String {
if let Some(stripped) = s.strip_prefix(ELLIPSIS_PREFIX) {
format!("$...{}", stripped)
} else if let Some(stripped) = s.strip_prefix(METAVAR_PREFIX) {
format!("${}", stripped)
} else {
s.to_string()
}
}
fn extract_ellipsis_var(tokens: &str) -> Option<String> {
let trimmed = tokens.trim();
if trimmed.starts_with(ELLIPSIS_PREFIX) {
Some(to_metavar_name(trimmed))
} else if trimmed.starts_with(METAVAR_PREFIX) {
Some(to_metavar_name(trimmed))
} else {
None
}
}
fn path_to_string(path: &syn::Path) -> String {
path.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::")
}
fn binop_to_string(op: &syn::BinOp) -> &'static str {
match op {
syn::BinOp::Add(_) => "+",
syn::BinOp::Sub(_) => "-",
syn::BinOp::Mul(_) => "*",
syn::BinOp::Div(_) => "/",
syn::BinOp::Rem(_) => "%",
syn::BinOp::And(_) => "&&",
syn::BinOp::Or(_) => "||",
syn::BinOp::BitXor(_) => "^",
syn::BinOp::BitAnd(_) => "&",
syn::BinOp::BitOr(_) => "|",
syn::BinOp::Shl(_) => "<<",
syn::BinOp::Shr(_) => ">>",
syn::BinOp::Eq(_) => "==",
syn::BinOp::Lt(_) => "<",
syn::BinOp::Le(_) => "<=",
syn::BinOp::Ne(_) => "!=",
syn::BinOp::Ge(_) => ">=",
syn::BinOp::Gt(_) => ">",
syn::BinOp::AddAssign(_) => "+=",
syn::BinOp::SubAssign(_) => "-=",
syn::BinOp::MulAssign(_) => "*=",
syn::BinOp::DivAssign(_) => "/=",
syn::BinOp::RemAssign(_) => "%=",
syn::BinOp::BitXorAssign(_) => "^=",
syn::BinOp::BitAndAssign(_) => "&=",
syn::BinOp::BitOrAssign(_) => "|=",
syn::BinOp::ShlAssign(_) => "<<=",
syn::BinOp::ShrAssign(_) => ">>=",
_ => "?",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_preprocess() {
let parser = ConcreteParser::new();
assert_eq!(parser.preprocess("$var"), "__mv_var");
assert_eq!(
parser.preprocess("$receiver.unwrap()"),
"__mv_receiver.unwrap()"
);
assert_eq!(parser.preprocess("$...args"), "__ell_args");
assert_eq!(parser.preprocess("$a + $b"), "__mv_a + __mv_b");
}
#[test]
fn test_parse_method_call() {
let pattern = parse_pattern("$receiver.unwrap()").unwrap();
assert_eq!(pattern.node, NodeKind::MethodCall);
assert!(pattern.children.contains_key("receiver"));
assert!(pattern.children.contains_key("method"));
if let Some(PatternExpr::Capture(name)) = pattern.children.get("receiver") {
assert_eq!(name, "$receiver");
} else {
panic!("Expected receiver to be a capture");
}
}
#[test]
fn test_parse_method_call_with_args() {
let pattern = parse_pattern("$receiver.expect($msg)").unwrap();
assert_eq!(pattern.node, NodeKind::MethodCall);
assert!(pattern.children.contains_key("args"));
}
#[test]
fn test_parse_macro_call() {
let pattern = parse_pattern("panic!($msg)").unwrap();
assert_eq!(pattern.node, NodeKind::MacroCall);
assert!(pattern.children.contains_key("name"));
}
#[test]
fn test_parse_function_call() {
let pattern = parse_pattern("foo($a, $b)").unwrap();
assert_eq!(pattern.node, NodeKind::FunctionCall);
}
#[test]
fn test_parse_binary_op() {
let pattern = parse_pattern("$a + $b").unwrap();
assert_eq!(pattern.node, NodeKind::BinaryOp);
assert!(pattern.children.contains_key("left"));
assert!(pattern.children.contains_key("right"));
if let Some(PatternExpr::Capture(name)) = pattern.children.get("left") {
assert_eq!(name, "$a");
}
if let Some(PatternExpr::Capture(name)) = pattern.children.get("right") {
assert_eq!(name, "$b");
}
}
#[test]
fn test_parse_if_expr() {
let pattern = parse_pattern("if $cond { }").unwrap();
assert_eq!(pattern.node, NodeKind::If);
assert!(pattern.children.contains_key("condition"));
}
#[test]
fn test_parse_try_expr() {
let pattern = parse_pattern("$expr?").unwrap();
assert_eq!(pattern.node, NodeKind::Try);
}
#[test]
fn test_metavariable_detection() {
assert!(is_metavariable("__mv_VAR"));
assert!(is_metavariable("__mv_receiver"));
assert!(is_metavariable("__ell_args"));
assert!(!is_metavariable("var"));
}
#[test]
fn test_ellipsis_detection() {
assert!(is_ellipsis_metavariable("__ell_args"));
assert!(!is_ellipsis_metavariable("__mv_args"));
}
#[test]
fn test_to_metavar_name() {
assert_eq!(to_metavar_name("__mv_receiver"), "$receiver");
assert_eq!(to_metavar_name("__ell_args"), "$...args");
assert_eq!(to_metavar_name("normal"), "normal");
}
#[test]
fn test_parse_qualified_path() {
let pattern = parse_pattern("Filter::Recurse").unwrap();
assert_eq!(pattern.node, NodeKind::Path);
if let Some(PatternExpr::Name(NameMatcher::Exact(path))) = pattern.children.get("path") {
assert_eq!(path, "Filter::Recurse");
} else {
panic!(
"Expected Name(Exact(\"Filter::Recurse\")) under \"path\" key, got: {:?}",
pattern.children
);
}
}
#[test]
fn test_parse_simple_path() {
let pattern = parse_pattern("foo").unwrap();
assert_eq!(pattern.node, NodeKind::Path);
if let Some(PatternExpr::Name(NameMatcher::Exact(path))) = pattern.children.get("path") {
assert_eq!(path, "foo");
} else {
panic!("Expected Name(Exact(\"foo\")) under \"path\" key");
}
}
}