color_print_proc_macro/format_args/
mod.rs
1mod format_arg;
4
5use proc_macro::TokenStream;
6use syn::spanned::Spanned;
7use syn::{
8 self, parse::Parser, punctuated::Punctuated, token::Comma, Expr, ExprLit, Lit, LitStr, Token,
9};
10
11use crate::color_context::ColorTag;
12use crate::error::{Error, SpanError};
13use crate::parse;
14use crate::util::{self, inner_span};
15use format_arg::FormatArg;
16
17pub fn get_args_and_format_string(
19 input: TokenStream,
20) -> Result<(LitStr, Punctuated<FormatArg, Comma>), SpanError> {
21 let args = parse_args(input)?;
22 let format_string = get_format_string(args.first())?;
23 Ok((format_string, args))
24}
25
26pub fn parse_args(input: TokenStream) -> Result<Punctuated<FormatArg, Comma>, SpanError> {
28 let parser = Punctuated::<FormatArg, Token![,]>::parse_terminated;
29 parser
30 .parse(input)
31 .map_err(|e| Error::Parse(e.to_string()).into())
32}
33
34pub fn get_format_string(arg: Option<&FormatArg>) -> Result<LitStr, SpanError> {
36 match arg {
37 Some(FormatArg {
38 expr: Expr::Lit(ExprLit {
39 lit: Lit::Str(s), ..
40 }),
41 ..
42 }) => Ok(s.to_owned()),
43 Some(bad_arg) => {
44 Err(SpanError::new(Error::MustBeStringLiteral, Some(bad_arg.span()),))
45 }
46 None => Ok(util::literal_string(""))
47 }
48}
49
50#[derive(Debug)]
67pub enum Node<'a> {
68 Text(&'a str),
69 Placeholder(&'a str),
70 ColorTagGroup(Vec<ColorTag<'a>>),
71}
72
73pub fn parse_format_string<'a>(
76 input: &'a str,
77 lit_str: &LitStr,
78) -> Result<Vec<Node<'a>>, SpanError> {
79 enum Context {
82 Text(usize),
84 Placeholder(usize),
86 Color(usize),
88 }
89
90 macro_rules! span {
91 ($inside:expr) => { inner_span(input, lit_str, $inside) };
92 }
93 macro_rules! err {
94 ([$inside:expr] $($e:tt)*) => { SpanError::new($($e)*, Some(span!($inside))) };
95 ($($e:tt)*) => { SpanError::new($($e)*, Some(lit_str.span())) };
96 }
97
98 let mut context = Context::Text(0);
99 let mut nodes = vec![];
100 let mut close_angle_bracket_idx: Option<usize> = None;
101 let mut nb_open_tags: isize = 0;
102
103 for (i, c) in input.char_indices() {
104 match context {
105 Context::Text(text_start) => {
106 let mut push_text = false;
107 if c == '{' {
108 context = Context::Placeholder(i);
110 push_text = true;
111 } else if c == '<' {
112 context = Context::Color(i);
114 push_text = true;
115 } else if c == '>' {
116 if let Some(idx) = close_angle_bracket_idx {
118 if i == idx + 1 {
119 context = Context::Text(i + 1);
120 push_text = true;
121 }
122 close_angle_bracket_idx = None;
123 } else {
124 close_angle_bracket_idx = Some(i);
125 }
126 };
127 if push_text && text_start != i {
128 nodes.push(Node::Text(&input[text_start..i]));
129 }
130 }
131 Context::Placeholder(ph_start) => {
132 if c == '{' && i == ph_start + 1 {
133 context = Context::Text(ph_start);
135 } else if c == '}' {
136 nodes.push(Node::Placeholder(&input[ph_start..i + 1]));
138 context = Context::Text(i + 1);
139 }
140 }
141 Context::Color(tag_start) => {
142 if c == '<' && i == tag_start + 1 {
143 context = Context::Text(tag_start + 1);
145 } else if c == '>' {
146 let tag_input = &input[tag_start..i + 1];
148 let mut tag = parse::color_tag(tag_input)
149 .map_err(|e| {
150 use nom::Err;
151 let (input, error) = match e {
152 Err::Error(parse::Error { detail: Some(d), .. }) |
153 Err::Failure(parse::Error { detail: Some(d), .. }) => {
154 (d.input, Error::ParseTag(d.message))
155 }
156 _ => (tag_input, Error::UnableToParseTag(tag_input.to_string())),
158 };
159 err!([input] error)
160 })?
161 .1;
162 tag.set_span(span!(tag_input));
163 nb_open_tags += if tag.is_close { -1 } else { 1 };
164 if let Some(Node::ColorTagGroup(last_tag_group)) = nodes.last_mut() {
168 last_tag_group.push(tag);
169 } else {
170 nodes.push(Node::ColorTagGroup(vec![tag]));
171 }
172 context = Context::Text(i + 1);
173 }
174 }
175 }
176 }
177
178 match context {
180 Context::Text(text_start) => {
181 if text_start != input.len() {
182 nodes.push(Node::Text(&input[text_start..]));
183 }
184
185 if nb_open_tags > 0 {
187 let tags = (0..nb_open_tags)
188 .map(|_| ColorTag::new_close())
189 .collect::<Vec<_>>();
190 nodes.push(Node::ColorTagGroup(tags));
191 }
192
193 Ok(nodes)
194 }
195 Context::Placeholder(start) => Err(err!([&input[start..]] Error::UnclosedPlaceholder)),
196 Context::Color(start) => Err(err!([&input[start..]] Error::UnclosedTag)),
197 }
198}