1use std::collections::HashMap;
27use std::fs;
28use std::path::Path;
29use std::sync::Arc;
30
31use p4::check::Diagnostics;
32use p4::{
33 ast::AST, check, error, error::SemanticError, lexer, parser, preprocessor,
34};
35use proc_macro::TokenStream;
36use serde::Deserialize;
37use serde_tokenstream::ParseWrapper;
38use syn::{parse, LitStr};
39
40#[derive(Deserialize)]
41struct MacroSettings {
42 p4: ParseWrapper<LitStr>,
43 pipeline_name: ParseWrapper<LitStr>,
44}
45
46struct GenerationSettings {
47 pipeline_name: String,
48}
49
50impl Default for GenerationSettings {
51 fn default() -> Self {
52 Self {
53 pipeline_name: "main".to_owned(),
54 }
55 }
56}
57
58#[proc_macro]
66pub fn use_p4(item: TokenStream) -> TokenStream {
67 match do_use_p4(item) {
68 Err(err) => err.to_compile_error().into(),
69 Ok(out) => out,
70 }
71}
72
73fn do_use_p4(item: TokenStream) -> Result<TokenStream, syn::Error> {
74 let (filename, settings) =
75 if let Ok(filename) = parse::<LitStr>(item.clone()) {
76 (filename.value(), GenerationSettings::default())
77 } else {
78 let MacroSettings { p4, pipeline_name } =
79 serde_tokenstream::from_tokenstream(&item.into())?;
80 (
81 p4.into_inner().value(),
82 GenerationSettings {
83 pipeline_name: pipeline_name.into_inner().value(),
84 },
85 )
86 };
87
88 generate_rs(filename, settings).map(Into::into)
89}
90
91fn generate_rs(
92 filename: String,
93 settings: GenerationSettings,
94) -> Result<proc_macro2::TokenStream, syn::Error> {
95 let mut ast = AST::default();
98 let mut sources = HashMap::new();
99 process_file(Arc::new(filename), &mut ast, &mut sources)?;
100 p4_rust::sanitize(&mut ast);
101
102 let (hlir, diags) = check::all(&ast);
103 check(&sources, &diags)?;
104
105 let tokens = p4_rust::emit_tokens(
106 &ast,
107 &hlir,
108 p4_rust::Settings {
109 pipeline_name: settings.pipeline_name.clone(),
110 },
111 );
112
113 Ok(tokens)
114}
115
116fn process_file(
117 filename: Arc<String>,
118 ast: &mut AST,
119 sources: &mut HashMap<Arc<String>, Vec<String>>,
120) -> Result<(), syn::Error> {
121 let contents = match fs::read_to_string(&*filename) {
122 Ok(c) => c,
123 Err(e) => panic!("failed to read file {}: {}", filename, e),
124 };
125 let ppr = preprocessor::run(&contents, filename.clone()).unwrap();
126 for included in &ppr.elements.includes {
127 let path = Path::new(included);
128 if !path.is_absolute() {
129 let parent = Path::new(&*filename).parent().unwrap();
130 let joined = parent.join(included);
131 process_file(
132 Arc::new(joined.to_str().unwrap().to_string()),
133 ast,
134 sources,
135 )?
136 } else {
137 process_file(Arc::new(included.clone()), ast, sources)?;
138 }
139 }
140
141 let lines: Vec<&str> = ppr.lines.iter().map(|x| x.as_str()).collect();
142 let lxr = lexer::Lexer::new(lines, filename.clone());
143 let mut psr = parser::Parser::new(lxr);
144 psr.run(ast).unwrap();
145 sources.insert(filename, ppr.lines);
146 Ok(())
147}
148
149fn check(
151 sources: &HashMap<Arc<String>, Vec<String>>,
152 diagnostics: &Diagnostics,
153) -> Result<(), syn::Error> {
154 let errors = diagnostics.errors();
155 if !errors.is_empty() {
156 let mut err = Vec::new();
157 for e in errors {
158 err.push(SemanticError {
159 at: e.token.clone(),
160 message: e.message.clone(),
161 source: sources
162 .get(&e.token.file)
163 .and_then(|lines| lines.get(e.token.line))
164 .cloned()
165 .unwrap_or_default(),
166 });
167 }
168 return Err(syn::Error::new(
169 proc_macro2::Span::call_site(),
170 error::Error::Semantic(err).to_string(),
171 ));
172 }
173 Ok(())
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 fn generate(path: &Path) -> Result<proc_macro2::TokenStream, syn::Error> {
181 let settings = GenerationSettings::default();
182 generate_rs(path.to_str().unwrap().into(), settings)
183 }
184
185 #[test]
186 fn macro_rejects_width_in_final_file() {
187 let dir = tempfile::tempdir().unwrap();
188 let path = dir.path().join("wide.p4");
189 fs::write(&path, "header wide_t {\n bit<129> field;\n}\n").unwrap();
190
191 let error = generate(&path).expect_err("bit<129> accepted").to_string();
192
193 assert!(error.contains("Width 129 exceeds the 128-bit compiler limit"));
194 assert!(error.contains("bit<129> field;"));
195 }
196
197 #[test]
198 fn macro_diagnostic_uses_included_source() {
199 let dir = tempfile::tempdir().unwrap();
200 let path = dir.path().join("main.p4");
201 let included = dir.path().join("wide.p4");
202 fs::write(&path, "#include <wide.p4>\n").unwrap();
203 fs::write(
204 &included,
205 "\n\n\nheader wide_t {\n bit<129> included_field;\n}\n",
206 )
207 .unwrap();
208
209 let error = generate(&path).expect_err("bit<129> accepted").to_string();
210
211 assert!(error.contains("Width 129 exceeds the 128-bit compiler limit"));
212 assert!(error.contains("bit<129> included_field;"));
213 assert!(error.contains(included.to_str().unwrap()));
214 }
215
216 #[test]
217 fn macro_checks_complete_program_after_includes() {
218 let dir = tempfile::tempdir().unwrap();
219 let path = dir.path().join("main.p4");
220 fs::write(
221 &path,
222 "#include <structs.p4>\nheader header_t {\n bit<8> field;\n}\n",
223 )
224 .unwrap();
225 fs::write(
226 dir.path().join("structs.p4"),
227 "struct headers_t {\n header_t hdr;\n}\n",
228 )
229 .unwrap();
230
231 generate(&path).expect("complete program rejected");
232 }
233}