1use std::collections::HashSet;
7
8use yog_data::Diagnostic;
9use yog_script_analyze::ast::{Decl, DeclKind, File, TypeKind};
10use yog_script_analyze::macros::ProcMacros;
11use yog_script_analyze::system::{self, DERIVE_MACRO, FIELD, ITEM, MACRO, PROC_MACRO};
12use yog_script_run::{Interp, Panic, Program, Value};
13
14pub struct MacroLibrary {
16 files: Vec<File>,
17 calls: HashSet<String>,
18 derives: HashSet<String>,
19 attributes: HashSet<String>,
21}
22
23impl MacroLibrary {
24 pub fn load(sources: &[(String, String)]) -> Result<Self, Vec<Diagnostic>> {
26 let mut lib = MacroLibrary {
27 files: Vec::new(),
28 calls: HashSet::new(),
29 derives: HashSet::new(),
30 attributes: HashSet::new(),
31 };
32 let mut problems = Vec::new();
33 for (name, text) in sources {
34 let checked = yog_script_analyze::check(name, text);
35 problems.extend(checked.problems.into_iter().filter(Diagnostic::is_error));
36 lib.files.push(checked.file);
37 }
38 if !problems.is_empty() {
39 return Err(problems);
40 }
41 for d in lib.files.iter().flat_map(|f| &f.decls) {
42 match &d.kind {
43 DeclKind::Method(m) if marked(d, PROC_MACRO) => {
44 lib.calls.insert(m.name.text.clone());
45 }
46 DeclKind::Method(m) if marked(d, DERIVE_MACRO) => {
47 lib.derives.insert(m.name.text.clone());
48 }
49 DeclKind::Type { name, .. } if marked(d, MACRO) => {
50 lib.attributes.insert(name.text.clone());
51 }
52 _ => {}
53 }
54 }
55 Ok(lib)
56 }
57
58 fn run<R>(&self, f: impl FnOnce(&mut Interp<'_>) -> R) -> R {
60 let mut sources: Vec<&[Decl]> = vec![&system::DECLS];
61 sources.extend(self.files.iter().map(|f| f.decls.as_slice()));
62 let program = Program::of(&sources);
63 f(&mut Interp::new(&program))
64 }
65}
66
67impl ProcMacros for MacroLibrary {
68 fn call(&self, name: &str, input: &str) -> Option<Result<String, String>> {
69 self.calls
70 .contains(name)
71 .then(|| self.run(|i| code(i.call(name, vec![Value::str(input)]))))
72 }
73
74 fn derive(&self, name: &str, item: &str) -> Option<Result<String, String>> {
75 if !self.derives.contains(name) {
76 return None;
77 }
78 Some(self.run(|i| {
79 let item = item_value(i, item)?;
80 code(i.call(name, vec![item]))
81 }))
82 }
83
84 fn attribute(&self, name: &str, args: &str, item: &str) -> Option<Result<String, String>> {
85 if !self.attributes.contains(name) {
86 return None;
87 }
88 Some(self.run(|i| {
89 let item = item_value(i, item)?;
90 code(i.call_static(name, EXPAND, vec![Value::str(args), item]))
91 }))
92 }
93}
94
95const EXPAND: &str = "Expand";
98
99fn marked(d: &Decl, name: &str) -> bool {
101 d.attributes
102 .iter()
103 .any(|a| a.path.last().is_some_and(|n| n.text == name))
104}
105
106fn code(out: Result<Value, Panic>) -> Result<String, String> {
108 match out {
109 Ok(Value::Str(s)) => Ok(s.to_string()),
110 Ok(other) => Err(format!("a macro gives its code as a string, not `{other}`")),
111 Err(p) => Err(p.message),
112 }
113}
114
115fn item_value(i: &mut Interp<'_>, text: &str) -> Result<Value, String> {
118 let parsed = yog_script_analyze::parse("item.ys", text);
119 let Some((name, kind, fields)) = parsed.file.decls.iter().find_map(|d| shape(d, text)) else {
120 return Err("a derive macro is for a type".to_string());
121 };
122 let mut made = Vec::new();
123 for (n, t) in fields {
124 made.push(
125 i.make_object(FIELD, vec![Value::str(&n), Value::str(&t)])
126 .map_err(|p| p.message)?,
127 );
128 }
129 let args = vec![
130 Value::str(&name),
131 Value::str(&kind),
132 Value::list(made),
133 Value::str(text),
134 ];
135 i.make_object(ITEM, args).map_err(|p| p.message)
136}
137
138type Shape = (String, String, Vec<(String, String)>);
140
141fn shape(d: &Decl, text: &str) -> Option<Shape> {
143 let written = |t: &yog_script_analyze::ast::Type| {
144 text.get(t.span.start as usize..t.span.end as usize)
145 .unwrap_or_default()
146 .to_string()
147 };
148 match &d.kind {
149 DeclKind::Type {
150 word,
151 name,
152 header,
153 body,
154 ..
155 } => {
156 let mut fields: Vec<(String, String)> = header
157 .ctor
158 .iter()
159 .flatten()
160 .map(|p| (p.name.text.clone(), written(&p.ty)))
161 .collect();
162 for m in body {
163 match &m.kind {
164 DeclKind::Field(f) => fields.push((f.name.text.clone(), written(&f.ty))),
165 DeclKind::Property { ty, name, .. }
166 if !matches!(ty.kind, TypeKind::Lifetime(_)) =>
167 {
168 fields.push((name.text.clone(), written(ty)));
169 }
170 _ => {}
171 }
172 }
173 Some((name.text.clone(), word.text.clone(), fields))
174 }
175 DeclKind::Enum { name, .. } => Some((
176 name.text.clone(),
177 yog_script_analyze::words::r#enum::WORD.text.to_string(),
178 Vec::new(),
179 )),
180 _ => None,
181 }
182}