yog_script_analyze/macros/
includes.rs1use yog_analysis::Report;
7use yog_ids::script_problem;
8
9use super::builtin::INCLUDE_STR;
10use crate::source::token::{Punct, StrPart, Tok, Token};
11
12pub trait ModFiles {
14 fn read(&self, source: &str, path: &str) -> Result<String, String>;
17}
18
19pub(crate) fn expand(
22 mut tokens: Vec<Token>,
23 (source, files): (&str, &dyn ModFiles),
24 report: &mut Report<'_>,
25) -> Vec<Token> {
26 let mut i = 0;
27 while i < tokens.len() {
28 let Some(path) = call_at(&tokens, i) else {
29 i += 1;
30 continue;
31 };
32 let at = tokens[i].span.to(tokens[i + CALL_LEN - 1].span);
33 match files.read(source, &path) {
34 Ok(text) => {
35 let lit = Token {
36 tok: Tok::RawStr(text),
37 span: at,
38 };
39 tokens.splice(i..i + CALL_LEN, [lit]);
40 }
41 Err(why) => {
42 report.error(
43 script_problem::MACRO,
44 at,
45 format!("`{}!` can't read `{path}`: {why}", INCLUDE_STR.name),
46 );
47 i += CALL_LEN;
48 }
49 }
50 i += 1;
51 }
52 tokens
53}
54
55const CALL_LEN: usize = 5;
57
58fn call_at(tokens: &[Token], i: usize) -> Option<String> {
60 let call = tokens.get(i..i + CALL_LEN)?;
61 let named = matches!(&call[0].tok, Tok::Ident(n) if n == INCLUDE_STR.name);
62 let shaped = call[1].tok == Tok::Punct(Punct::Bang)
63 && call[2].tok == Tok::Punct(Punct::LParen)
64 && call[4].tok == Tok::Punct(Punct::RParen);
65 if !named || !shaped {
66 return None;
67 }
68 match &call[3].tok {
69 Tok::RawStr(s) => Some(s.clone()),
70 Tok::Str(parts) => match parts.as_slice() {
71 [StrPart::Text(s)] => Some(s.clone()),
72 [] => Some(String::new()),
73 _ => None,
74 },
75 _ => None,
76 }
77}