backhopper_core/model/
macro_decl.rs1use std::fmt;
8use std::str::FromStr;
9
10use backhopper_erlang_scan::{count_top_level_commas, take_balanced_parens};
11use serde::{Deserialize, Serialize};
12
13use crate::errors::NameError;
14use crate::model::names::{Arity, MacroName};
15use crate::model::normalised::NormalisedMacroBody;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
20pub struct MacroKey {
21 pub name: MacroName,
22 pub arity: Option<Arity>,
23}
24
25impl MacroKey {
26 #[must_use]
27 pub fn object_like(name: MacroName) -> Self {
28 Self { name, arity: None }
29 }
30
31 #[must_use]
32 pub fn function_like(name: MacroName, arity: Arity) -> Self {
33 Self {
34 name,
35 arity: Some(arity),
36 }
37 }
38
39 #[must_use]
40 pub fn parse_define(body: &str) -> Option<(Self, &str)> {
41 let trimmed = body.trim_start();
42 let name_end = trimmed
43 .char_indices()
44 .find(|(_, c)| !(c.is_ascii_alphanumeric() || *c == '_' || *c == '@'))
45 .map_or(trimmed.len(), |(i, _)| i);
46 let name = MacroName::new(&trimmed[..name_end]).ok()?;
47 let rest = trimmed[name_end..].trim_start();
48 let (arity, after) = if rest.starts_with('(') {
49 let (args, after_args) = take_balanced_parens(rest)?;
50 let count = if args.trim().is_empty() {
51 0
52 } else {
53 count_top_level_commas(args) + 1
54 };
55 (Some(Arity::try_from(count).ok()?), after_args.trim_start())
56 } else {
57 (None, rest)
58 };
59 let value = after.strip_prefix(',')?;
60 Some((Self { name, arity }, value))
61 }
62}
63
64impl fmt::Display for MacroKey {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 match self.arity {
67 Some(arity) => write!(f, "{}/{arity}", self.name),
68 None => write!(f, "{}", self.name),
69 }
70 }
71}
72
73impl FromStr for MacroKey {
74 type Err = NameError;
75
76 fn from_str(s: &str) -> Result<Self, NameError> {
77 match s.split_once('/') {
78 Some((name, arity)) => Ok(Self::function_like(name.parse()?, arity.parse()?)),
79 None => Ok(Self::object_like(s.parse()?)),
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
88pub enum MacroBody {
89 Text(NormalisedMacroBody),
90 Conditional,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
94pub struct MacroDecl {
95 pub key: MacroKey,
96 pub body: MacroBody,
97}
98
99impl MacroDecl {
100 #[must_use]
101 pub fn merge(self, other: Self) -> Self {
102 if self.body == other.body {
103 return self;
104 }
105 Self {
106 key: self.key,
107 body: MacroBody::Conditional,
108 }
109 }
110}