1use quote::quote;
2use syn::parse_macro_input;
3use syn::Token;
4
5#[proc_macro]
6#[doc(hidden)]
7pub fn gen_csi(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
8 parse_macro_input!(input as GenCsi).tts.into()
9}
10
11struct GenCsi {
12 tts: proc_macro2::TokenStream,
13}
14
15impl syn::parse::Parse for GenCsi {
16 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
17 let csis = syn::punctuated::Punctuated::<CsiParse, Token![;]>::parse_terminated(input)?;
18 let tts = csis
19 .iter()
20 .map(|csi| {
21 let tts = &csi.tts;
22 quote!(#tts)
23 })
24 .collect::<proc_macro2::TokenStream>();
25 Ok(GenCsi { tts })
26 }
27}
28
29struct CsiParse {
30 tts: proc_macro2::TokenStream,
31}
32
33impl syn::parse::Parse for CsiParse {
34 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
35 let visi = input.parse::<syn::Visibility>()?;
36 let nm = input.parse::<syn::Ident>()?;
37 let _fat_arrow = input.parse::<Token![=>]>()?;
38 let CsiFmtParse { doc, fmt, nms_ord } = input.parse::<CsiFmtParse>()?;
39
40 let args = {
41 #[derive(Clone)]
42 struct Arg {
43 nm: proc_macro2::Ident,
44 ty: proc_macro2::Ident,
45 }
46 let mut args = Vec::<Arg>::with_capacity(nms_ord.len());
47 for _ in 0..nms_ord.len() {
48 let _comma = input.parse::<Token![,]>()?;
49 let CsiArgParse { nm, ty } = input.parse::<CsiArgParse>()?;
50 args.push(Arg { nm, ty });
51 }
52 if nms_ord.len() != args.len() {
53 return Err(syn::Error::new(
54 proc_macro2::Span::call_site(),
55 "unmatch args' count",
56 ));
57 }
58 args
59 };
60
61 let arg_nms = {
62 let mut nms = Vec::<proc_macro2::Ident>::with_capacity(args.len());
63 let mut args = args.clone(); for ref nm_ord in nms_ord {
65 let idx = args
66 .iter()
67 .enumerate()
68 .find_map(|(i, arg)| (nm_ord == &arg.nm.to_string()).then_some(i));
69 let Some(idx) = idx else {
70 return Err(syn::Error::new(proc_macro2::Span::call_site(), "unmatch args' name"));
71 };
72 let arg = args.remove(idx);
73 nms.push(arg.nm);
74 }
75 nms
76 };
77
78 let tts = {
79 let doc = format!("`\\x1b[{}`", doc);
80 let arg_exprs = args.iter().map(|arg| {
81 let nm = &arg.nm;
82 let ty = &arg.ty;
83 quote! { #nm: #ty }
84 });
85 let ret = {
86 if args.is_empty() {
87 quote! { Csi(std::borrow::Cow::from(#fmt)) }
88 } else {
89 quote! { Csi(std::borrow::Cow::from(std::format!(#fmt, #(#arg_nms,)*))) }
90 }
91 };
92 quote! {
93 #[doc = #doc]
94 #visi fn #nm (#(#arg_exprs,)*) -> Csi<'static> {
95 #ret
96 }
97 }
98 };
99 Ok(CsiParse { tts })
100 }
101}
102
103struct CsiArgParse {
104 nm: proc_macro2::Ident,
105 ty: proc_macro2::Ident,
106}
107
108impl syn::parse::Parse for CsiArgParse {
109 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
110 static INT_TYS: [&str; 6] = ["u8", "u16", "u32", "u64", "u128", "usize"];
111 let nm = input.parse::<proc_macro2::Ident>()?;
112 let Ok(_colon) = input.parse::<Token![:]>() else {
113 let ty = proc_macro2::Ident::new("u16", proc_macro2::Span::call_site());
114 return Ok(CsiArgParse { nm, ty })
115 };
116 let ty = input.parse::<proc_macro2::Ident>()?;
117 if !INT_TYS.contains(&ty.to_string().as_str()) {
118 let msg = format!("expect {:?}", INT_TYS);
119 return Err(syn::Error::new_spanned(ty, msg));
120 }
121 Ok(CsiArgParse { nm, ty })
122 }
123}
124
125struct CsiFmtParse {
126 doc: String,
127 fmt: proc_macro2::Literal,
128 nms_ord: Vec<String>,
129}
130
131impl syn::parse::Parse for CsiFmtParse {
132 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
133 let litstr = input.parse::<syn::LitStr>()?;
134 let s = litstr.value();
135 let mut bytes = s.bytes().peekable();
136 let mut nms_ord = Vec::<String>::new();
137 let mut docbuf = Vec::<u8>::with_capacity(s.len() * 2);
138 let mut fmtbuf = {
139 let mut v = Vec::<u8>::with_capacity(s.len() + 2);
140 v.push(b'\x1b');
141 v.push(b'[');
142 v
143 };
144 let mut nmbuf = Vec::<u8>::new();
145
146 while let Some(byte) = bytes.next() {
147 match byte {
148 b @ b'{' => {
149 docbuf.push(b);
150 fmtbuf.push(b);
151 'cb: loop {
152 match bytes.next() {
153 None => {
154 return Err(syn::Error::new_spanned(
155 litstr,
156 "expect a closing brace `}`",
157 ));
158 }
159 Some(b @ b'{') => {
160 fmtbuf.push(b);
161 break 'cb;
162 }
163 Some(b @ b'}') => {
164 docbuf.push(b);
165 fmtbuf.push(b);
166 let nm = if nmbuf.is_empty() {
167 return Err(syn::Error::new_spanned(
168 litstr,
169 "expect arg name inside the `{}`",
170 ));
171 } else {
172 let bytes = nmbuf.drain(..).collect::<Vec<u8>>();
173 String::from_utf8(bytes).unwrap()
174 };
175 nms_ord.push(nm);
176 break 'cb;
177 }
178 Some(b) => {
179 docbuf.push(b);
180 nmbuf.push(b);
181 continue;
182 }
183 }
184 }
185 }
186 b @ b'}' => {
187 let Some(b'}') = bytes.next() else{
188 return Err(syn::Error::new_spanned(
189 litstr,
190 "expect a `{` before a `}`",
191 ));
192 };
193 docbuf.push(b);
194 fmtbuf.push(b);
195 fmtbuf.push(b);
196 }
197 b => {
198 docbuf.push(b);
199 fmtbuf.push(b);
200 continue;
201 }
202 };
203 }
204 let doc = String::from_utf8(docbuf).unwrap();
205 let fmt = {
206 let s = String::from_utf8(fmtbuf).unwrap();
207 proc_macro2::Literal::string(&s)
208 };
209 Ok(CsiFmtParse { doc, fmt, nms_ord })
210 }
211}
212
213#[doc(hidden)]
242#[proc_macro]
243pub fn gen_clr_const(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
244 parse_macro_input!(input as GenClrConst).tts.into()
245}
246
247struct GenClrConst {
248 tts: proc_macro2::TokenStream,
249}
250
251impl syn::parse::Parse for GenClrConst {
252 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
253 let mut val = {
254 let lit = input.parse::<syn::LitInt>()?;
255 lit.base10_parse::<u8>()?
256 };
257 let _fat_arrow = input.parse::<Token![=>]>()?;
258 let tts = syn::punctuated::Punctuated::<syn::Expr, Token![,]>::parse_terminated(input)?
259 .into_iter()
260 .map(|expr| {
261 let ident = {
262 let mut tts = (quote!(#expr)).into_iter();
263 match (tts.next(), tts.next()) {
264 (Some(ident), None) => ident,
265 (None, Some(_)) => unreachable!(),
266 _ => {
267 return syn::Error::new_spanned(expr, "expect ident")
268 .into_compile_error();
269 }
270 }
271 };
272 let (fg, bg) = {
273 let s = ident.to_string();
274 let fg = proc_macro2::Ident::new(&format!("FG_{}", s), ident.span());
275 let bg = proc_macro2::Ident::new(&format!("BG_{}", s), ident.span());
276 (fg, bg)
277 };
278 let (fgval, bgval) = {
279 let fg = proc_macro2::Literal::u8_unsuffixed(val);
280 let bg = proc_macro2::Literal::u8_unsuffixed(val + 10);
281 val += 1;
282 (fg, bg)
283 };
284 quote! {
285 pub const #fg: u8 = #fgval;
286 pub const #bg: u8 = #bgval;
287 }
288 })
289 .collect::<proc_macro2::TokenStream>();
290 Ok(GenClrConst { tts })
291 }
292}
293
294#[doc(hidden)]
297#[proc_macro]
298pub fn gen_sty_const(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
299 parse_macro_input!(input as GenStyConst).tts.into()
300}
301
302struct GenStyConst {
303 tts: proc_macro2::TokenStream,
304}
305
306impl syn::parse::Parse for GenStyConst {
307 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
308 let mut val = {
309 let lit = input.parse::<syn::LitInt>()?;
310 lit.base10_parse::<u8>()?
311 };
312 let tts = syn::punctuated::Punctuated::<syn::Expr, Token![,]>::parse_terminated(input)?
313 .into_iter()
314 .map(|expr| {
315 let ident = {
316 let mut tts = (quote!(#expr)).into_iter();
317 match (tts.next(), tts.next()) {
318 (Some(ident), None) => ident,
319 (None, Some(_)) => unreachable!(),
320 _ => {
321 return syn::Error::new_spanned(expr, "expect ident")
322 .into_compile_error();
323 }
324 }
325 };
326 let (set, unset) = {
327 let s = ident.to_string();
328 let set = proc_macro2::Ident::new(&format!("STY_{}_SET", s), ident.span());
329 let unset = proc_macro2::Ident::new(&format!("STY_{}_RST", s), ident.span());
330 (set, unset)
331 };
332 let (setval, unsetval) = {
333 let set = proc_macro2::Literal::u8_unsuffixed(val);
334 let unset = proc_macro2::Literal::u8_unsuffixed(val + 10);
335 val += 1;
336 (set, unset)
337 };
338 quote! {
339 pub const #set: u8 = #setval;
340 pub const #unset: u8 = #unsetval;
341 }
342 })
343 .collect::<proc_macro2::TokenStream>();
344 Ok(GenStyConst { tts })
345 }
346}
347
348#[proc_macro]
374pub fn sgr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
375 parse_macro_input!(input as Sgr).tts.into()
376}
377
378struct Sgr {
379 tts: proc_macro2::TokenStream,
380}
381
382impl syn::parse::Parse for Sgr {
383 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
384 let (fmt, exprs) = {
385 let exprs =
386 syn::punctuated::Punctuated::<syn::Expr, Token![,]>::parse_terminated(input)?;
387 if exprs.is_empty() {
388 let err = syn::parse::Error::new_spanned(exprs, "expect at least one expression");
389 return Err(err);
390 };
391 let mut buf = Vec::<&str>::with_capacity(2 + (exprs.len() * 2));
392 buf.push("\x1b[");
393 for i in 0..exprs.len() {
394 buf.push("{}");
395 if i == exprs.len() - 1 {
396 buf.push("m");
397 } else {
398 buf.push(";");
399 }
400 }
401 (buf.concat(), exprs.into_iter())
402 };
403 let tts = quote! { etty::csi::Csi(std::borrow::Cow::from(std::format!(#fmt, #(#exprs as u8,)*))) };
404 Ok(Sgr { tts })
405 }
406}
407
408#[proc_macro]
429pub fn out(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
430 struct Out(proc_macro2::TokenStream);
431 impl syn::parse::Parse for Out {
432 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
433 let fmtargs = input.parse::<FmtArgsExprs>()?.0;
434 let tts = quote! {{
435 use std::io::Write;
436 std::write!(std::io::stdout(), #fmtargs).unwrap();
437 }};
438 Ok(Out(tts))
439 }
440 }
441 parse_macro_input!(input as Out).0.into()
442}
443
444#[proc_macro]
446pub fn outln(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
447 struct Outln(proc_macro2::TokenStream);
448 impl syn::parse::Parse for Outln {
449 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
450 let fmtargs = input.parse::<FmtArgsExprs>()?.0;
451 let tts = quote! {{
452 use std::io::Write;
453 std::writeln!(std::io::stdout(), #fmtargs).unwrap();
454 }};
455 Ok(Outln(tts))
456 }
457 }
458 parse_macro_input!(input as Outln).0.into()
459}
460
461#[proc_macro]
463pub fn outf(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
464 struct Outf(proc_macro2::TokenStream);
465 impl syn::parse::Parse for Outf {
466 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
467 let fmtargs = input.parse::<FmtArgsExprs>()?.0;
468 let tts = quote! {{
469 use std::io::Write;
470 std::write!(std::io::stdout(), #fmtargs).unwrap();
471 std::io::stdout().flush().unwrap();
472 }};
473 Ok(Outf(tts))
474 }
475 }
476 parse_macro_input!(input as Outf).0.into()
477}
478
479struct FmtArgsExprs(proc_macro2::TokenStream);
480
481impl syn::parse::Parse for FmtArgsExprs {
482 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
483 let lead = input.parse::<syn::Expr>()?;
484 let tts = if let syn::Expr::Lit(syn::ExprLit {
485 lit: syn::Lit::Str(litstr),
486 ..
487 }) = lead
488 {
489 if !input.is_empty() {
490 let _comma = input.parse::<Token![,]>()?;
491 }
492 let exprs =
493 syn::punctuated::Punctuated::<syn::Expr, Token![,]>::parse_terminated(input)?
494 .into_iter();
495 quote! { #litstr, #(#exprs,)* }
496 } else {
497 quote! { "{}", #lead }
498 };
499 Ok(FmtArgsExprs(tts))
500 }
501}