1#![cfg_attr(feature = "unstable", feature(proc_macro_span))]
2
3extern crate proc_macro;
4
5use std::fs;
6use std::path::PathBuf;
7
8use proc_macro2::{Span, TokenStream};
9use syn::{Error, Result};
10
11use crate::parse::{IncludeInput, LitStrOrChar, WchInput, WchzInput};
12
13mod encode;
14mod parse;
15
16fn expand_macro<F: FnOnce() -> Result<TokenStream>>(f: F) -> proc_macro::TokenStream {
18 match f() {
19 Ok(expanded) => expanded.into(),
20 Err(err) => err.to_compile_error().into(),
21 }
22}
23
24#[proc_macro]
25pub fn wch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
26 let WchInput { ty, literal, .. } = syn::parse_macro_input!(input);
27
28 expand_macro(|| match literal {
29 LitStrOrChar::Str(lit) => Ok(encode::expand_str(ty, &lit.value())),
30 LitStrOrChar::Char(lit) => encode::expand_char(ty, lit),
31 })
32}
33
34#[proc_macro]
35pub fn wchz(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
36 let WchzInput { ty, literal, .. } = syn::parse_macro_input!(input);
37
38 expand_macro(|| {
39 let text = literal.value();
40
41 if text.as_bytes().contains(&0) {
42 return Err(Error::new(
43 literal.span(),
44 "C-style string cannot contain nul characters",
45 ));
46 }
47
48 Ok(encode::expand_str_c(ty, &text))
49 })
50}
51
52#[proc_macro]
53pub fn include_wch(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
54 let IncludeInput { ty, file_path, .. } = syn::parse_macro_input!(input);
55
56 expand_macro(|| {
57 let text = read_file(&file_path)?;
58
59 Ok(encode::expand_str(ty, &text))
60 })
61}
62
63#[proc_macro]
64pub fn include_wchz(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
65 let IncludeInput { ty, file_path, .. } = syn::parse_macro_input!(input);
66
67 expand_macro(|| {
68 let text = read_file(&file_path)?;
69
70 if text.as_bytes().contains(&0) {
71 return Err(Error::new(
72 file_path.span(),
73 "C-style string cannot contain nul characters",
74 ));
75 }
76
77 Ok(encode::expand_str_c(ty, &text))
78 })
79}
80
81fn read_file(path: &syn::LitStr) -> Result<String> {
82 let span = path.span();
83 let mut path = PathBuf::from(path.value());
84
85 if path.is_relative() {
87 let mut dir = call_site_dir(span)?;
89
90 dir.push(path);
92 path = dir;
93 }
94
95 match fs::read_to_string(&path) {
96 Ok(text) => Ok(text),
97 Err(err) => Err(Error::new(
98 span,
99 format_args!("couldn't read {}: {}", path.display(), err),
100 )),
101 }
102}
103
104#[cfg(feature = "unstable")]
106fn call_site_dir(_span: Span) -> Result<PathBuf> {
107 let call_site = Span::call_site().unwrap().source();
108 let source_file = call_site.source_file();
109
110 let mut path = source_file.path();
112 path.pop();
114
115 Ok(path)
116}
117
118#[cfg(not(feature = "unstable"))]
119fn call_site_dir(span: Span) -> Result<PathBuf> {
120 Err(Error::new(
121 span,
122 "including files by relative path requires the `unstable` feature",
123 ))
124}