backhopper_core/
erlang_macros.rs1use std::collections::BTreeMap;
13
14use backhopper_erlang_scan::{for_each_top_level_byte, is_bare_atom, remove_line_comments};
15
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
17pub struct MacroKey {
18 pub name: String,
19 pub arity: Option<u8>,
20}
21
22pub type MacroTable = BTreeMap<MacroKey, String>;
23
24pub fn parse_define(body: &str) -> Option<(MacroKey, String)> {
28 let s = body.trim();
29 let s = s
30 .strip_prefix('(')
31 .map(|x| x.strip_suffix(')').unwrap_or(x))
32 .unwrap_or(s);
33 let (name_part, value_part) = split_define_head(s)?;
34 let name_part = remove_line_comments(name_part);
36 let name_part = name_part.as_ref();
37 if let Some((name, params)) = parse_parameterized_head(name_part) {
38 let arity = u8::try_from(params).ok()?;
39 return Some((
40 MacroKey {
41 name,
42 arity: Some(arity),
43 },
44 value_part.trim().to_owned(),
45 ));
46 }
47 let name = name_part.trim().to_owned();
48 if name.is_empty() {
49 return None;
50 }
51 Some((MacroKey { name, arity: None }, value_part.trim().to_owned()))
52}
53
54fn split_define_head(s: &str) -> Option<(&str, &str)> {
56 let bytes = s.as_bytes();
57 let i = for_each_top_level_byte(s, |i| bytes[i] == b',')?;
58 Some((&s[..i], &s[i + 1..]))
59}
60
61fn parse_parameterized_head(s: &str) -> Option<(String, usize)> {
62 let s = s.trim();
63 let (name, after) = s.split_once('(')?;
64 let after = after.strip_suffix(')')?;
65 let name = name.trim().to_owned();
66 if name.is_empty() {
67 return None;
68 }
69 let trimmed = after.trim();
70 if trimmed.is_empty() {
71 return Some((name, 0));
72 }
73 let params = trimmed.split(',').count();
74 Some((name, params))
75}
76
77pub fn expand_value_macro_to_atom(table: &MacroTable, name: &str) -> Option<String> {
81 let body = table.get(&MacroKey {
82 name: name.to_owned(),
83 arity: None,
84 })?;
85 let trimmed = body.trim();
86 if is_bare_atom(trimmed) {
87 Some(trimmed.to_owned())
88 } else {
89 None
90 }
91}
92
93pub fn expand_value_macro_to_mf(table: &MacroTable, name: &str) -> Option<(String, String)> {
96 let body = table.get(&MacroKey {
97 name: name.to_owned(),
98 arity: None,
99 })?;
100 let trimmed = body.trim();
101 let (m, f) = trimmed.split_once(':')?;
102 let m = m.trim();
103 let f = f.trim();
104 if is_bare_atom(m) && is_bare_atom(f) {
105 Some((m.to_owned(), f.to_owned()))
106 } else {
107 None
108 }
109}