1use lalrpop_util::ParseError;
5use proc_macro::TokenStream;
6use proc_macro2::Span;
7use proc_macro_error2::{abort, abort_call_site, proc_macro_error};
8use quote::quote;
9use std::collections::HashMap;
10use std::fmt::Write;
11use std::fs;
12use std::path::{Path, PathBuf};
13use syn::{
14 parenthesized, parse, parse_macro_input, Expr, ExprLit, Ident, Lit, LitInt, LitStr, Token,
15};
16
17const MAX_PROGRAM_SIZE: usize = 1024;
22
23struct OptionsArgs {
24 ident: Ident,
25 expr: Expr,
26}
27
28impl syn::parse::Parse for OptionsArgs {
29 fn parse(stream: syn::parse::ParseStream) -> syn::parse::Result<Self> {
30 let ident = stream.parse()?;
31 let _equals: Token![=] = stream.parse()?;
32 let expr = stream.parse()?;
33
34 Ok(Self { ident, expr })
35 }
36}
37
38struct Options {
40 options: HashMap<String, (Ident, Expr)>,
41}
42
43impl Options {
44 fn validate(&self) -> Result<(), parse::Error> {
45 let valid_identifiers = ["max_program_size"];
47
48 for (name, (id, _)) in &self.options {
49 if !valid_identifiers.contains(&name.as_str()) {
50 abort!(
51 id,
52 "unknown identifier, expected one of {:?}",
53 valid_identifiers
54 );
55 }
56 }
57
58 Ok(())
59 }
60
61 fn get_max_program_size_or_default(&self) -> Result<Expr, parse::Error> {
62 if let Some(mps) = self.options.get("max_program_size") {
63 Ok(mps.1.clone())
64 } else {
65 Ok(Expr::Lit(ExprLit {
66 attrs: vec![],
67 lit: Lit::Int(LitInt::new("32", Span::call_site())),
68 }))
69 }
70 }
71}
72
73impl syn::parse::Parse for Options {
74 fn parse(stream: syn::parse::ParseStream) -> parse::Result<Self> {
75 let content;
77 parenthesized!(content in stream);
78
79 if !content.is_empty() {
80 let mut options = HashMap::new();
81
82 while !content.is_empty() {
83 let opt: OptionsArgs = content.parse()?;
84 options.insert(opt.ident.to_string(), (opt.ident, opt.expr));
85 let _trailing_comma: Option<Token![,]> = content.parse().ok();
86 }
87
88 let _trailing_comma: Option<Token![,]> = stream.parse().ok();
89
90 let s = Self { options };
91
92 s.validate()?;
93
94 Ok(s)
95 } else {
96 Ok(Self {
97 options: HashMap::new(),
98 })
99 }
100 }
101}
102
103struct SelectProgram {
104 name: String,
105 ident: LitStr,
106}
107
108impl syn::parse::Parse for SelectProgram {
109 fn parse(stream: syn::parse::ParseStream) -> parse::Result<Self> {
110 let content;
112 parenthesized!(content in stream);
113
114 let name: LitStr = content.parse::<LitStr>()?;
115
116 Ok(Self {
117 name: name.value(),
118 ident: name,
119 })
120 }
121}
122
123struct PioFileMacroArgs {
124 krate: Ident,
125 max_program_size: Expr,
126 program: String,
127 program_name: Option<(String, LitStr)>,
128 file_path: PathBuf,
129}
130
131impl syn::parse::Parse for PioFileMacroArgs {
132 fn parse(stream: syn::parse::ParseStream) -> syn::parse::Result<Self> {
133 let krate: Ident = stream.parse()?;
134 let _comma: Option<Token![,]> = stream.parse()?;
135
136 let mut program = String::new();
137 let mut file_path = PathBuf::new();
138
139 if let Ok(s) = stream.parse::<LitStr>() {
141 let path = s.value();
142 let path = Path::new(&path);
143
144 let pathbuf = {
145 let mut p = PathBuf::new();
146
147 if path.is_relative() {
148 if let Some(crate_dir) = std::env::var_os("CARGO_MANIFEST_DIR") {
149 p.push(crate_dir);
150 } else {
151 abort!(s, "Cannot find 'CARGO_MANIFEST_DIR' environment variable");
152 }
153 }
154
155 p.push(path);
156
157 p
158 };
159
160 if !pathbuf.exists() {
161 abort!(s, "the file '{}' does not exist", pathbuf.display());
162 }
163
164 file_path = pathbuf.to_owned();
165
166 match fs::read(pathbuf) {
167 Ok(content) => match std::str::from_utf8(&content) {
168 Ok(prog) => program = prog.to_string(),
169 Err(e) => {
170 abort!(s, "could parse file: '{}'", e);
171 }
172 },
173 Err(e) => {
174 abort!(s, "could not read file: '{}'", e);
175 }
176 }
177
178 let _trailing_comma: Option<Token![,]> = stream.parse().ok();
179 }
180
181 let mut select_program = None;
182 let mut options = Options {
183 options: HashMap::new(),
184 };
185
186 for _ in 0..2 {
187 if let Ok(ident) = stream.parse::<Ident>() {
188 match ident.to_string().as_str() {
189 "select_program" => {
190 let sp: SelectProgram = stream.parse()?;
192 select_program = Some(sp);
193 let _trailing_comma: Option<Token![,]> = stream.parse().ok();
194 }
195 "options" => {
196 let opt: Options = stream.parse()?;
198 options = opt;
199 let _trailing_comma: Option<Token![,]> = stream.parse().ok();
200 }
201 _ => abort!(ident, "expected one of 'options' or 'select_program'"),
202 }
203 }
204 }
205
206 if !stream.is_empty() {
207 abort!(stream.span(), "expected end of input");
208 }
209
210 let max_program_size = options.get_max_program_size_or_default()?;
212
213 Ok(Self {
214 krate,
215 program_name: select_program.map(|v| (v.name, v.ident)),
216 max_program_size,
217 program,
218 file_path,
219 })
220 }
221}
222
223struct PioAsmMacroArgs {
224 krate: Ident,
225 max_program_size: Expr,
226 program: String,
227}
228
229impl syn::parse::Parse for PioAsmMacroArgs {
230 fn parse(stream: syn::parse::ParseStream) -> syn::parse::Result<Self> {
231 let krate: Ident = stream.parse()?;
232 let _comma: Option<Token![,]> = stream.parse()?;
233
234 let mut program = String::new();
235
236 while let Ok(s) = stream.parse::<LitStr>() {
238 writeln!(&mut program, "{}", s.value()).unwrap();
239
240 let _trailing_comma: Option<Token![,]> = stream.parse().ok();
241 }
242
243 let mut options = Options {
246 options: HashMap::new(),
247 };
248
249 if let Ok(ident) = stream.parse::<Ident>() {
250 if ident == "options" {
251 let opt: Options = stream.parse()?;
252 options = opt;
253 let _trailing_comma: Option<Token![,]> = stream.parse().ok();
254 }
255 }
256
257 if !stream.is_empty() {
258 abort!(stream.span(), "expected end of input");
259 }
260
261 let max_program_size = options.get_max_program_size_or_default()?;
263
264 Ok(Self {
265 krate,
266 max_program_size,
267 program,
268 })
269 }
270}
271
272#[proc_macro]
273#[proc_macro_error]
274pub fn pio_file_inner(item: TokenStream) -> TokenStream {
275 let args = parse_macro_input!(item as PioFileMacroArgs);
276 let parsed_programs = pio_parser::Parser::<{ MAX_PROGRAM_SIZE }>::parse_file(&args.program);
277 let program = match &parsed_programs {
278 Ok(programs) => {
279 if let Some((program_name, ident)) = args.program_name {
280 if let Some(program) = programs.get(&program_name) {
281 program
282 } else {
283 abort! { ident, "program name not found in the provided file" }
284 }
285 } else {
286 match programs.len() {
289 0 => abort_call_site! { "no programs in the provided file" },
290 1 => programs.iter().next().unwrap().1,
291 _ => {
292 abort_call_site! { "more than 1 program in the provided file, select one using `select_program(\"my_program\")`" }
293 }
294 }
295 }
296 }
297 Err(e) => return parse_error(e, &args.program).into(),
298 };
299
300 to_codegen(
301 args.krate,
302 program,
303 args.max_program_size,
304 Some(
305 args.file_path
306 .into_os_string()
307 .into_string()
308 .expect("file path must be valid UTF-8"),
309 ),
310 )
311 .into()
312}
313
314#[proc_macro]
316#[proc_macro_error]
317pub fn pio_asm_inner(item: TokenStream) -> TokenStream {
318 let args = parse_macro_input!(item as PioAsmMacroArgs);
319
320 let parsed_program = pio_parser::Parser::<{ MAX_PROGRAM_SIZE }>::parse_program(&args.program);
321
322 let program = match &parsed_program {
323 Ok(program) => program,
324 Err(e) => return parse_error(e, &args.program).into(),
325 };
326
327 to_codegen(args.krate, program, args.max_program_size, None).into()
328}
329
330fn to_codegen(
331 krate: Ident,
332 program: &pio_core::ProgramWithDefines<HashMap<String, i32>, { MAX_PROGRAM_SIZE }>,
333 max_program_size: Expr,
334 file: Option<String>,
335) -> proc_macro2::TokenStream {
336 let pio_core::ProgramWithDefines {
337 program,
338 public_defines,
339 } = program;
340 if let Expr::Lit(ExprLit {
341 attrs: _,
342 lit: Lit::Int(i),
343 }) = &max_program_size
344 {
345 if let Ok(mps) = i.base10_parse::<usize>() {
346 if program.code.len() > mps {
347 abort_call_site!(
348 "the resulting program is larger than the maximum allowed: max = {}, size = {}",
349 mps,
350 program.code.len()
351 );
352 }
353 }
354 }
355
356 let origin = if let Some(origin) = program.origin {
357 quote!(Some(#origin))
358 } else {
359 quote!(None)
360 };
361
362 let code = &program.code;
363 let code = quote!(
364 ::core::iter::IntoIterator::into_iter([#(#code),*]).collect()
365 );
366
367 let wrap_source = program.wrap.source;
368 let wrap_target = program.wrap.target;
369 let wrap = quote!(
370 #krate::Wrap {source: #wrap_source, target: #wrap_target}
371 );
372
373 let side_set_optional = program.side_set.optional();
374 let side_set_bits = program.side_set.bits();
375 let side_set_pindirs = program.side_set.pindirs();
376 let side_set = quote!(
377 #krate::SideSet::new_from_proc_macro(
378 #side_set_optional,
379 #side_set_bits,
380 #side_set_pindirs,
381 )
382 );
383
384 let version = Ident::new(&format!("{:?}", program.version), Span::call_site());
385 let version = quote!(#krate::PioVersion::#version);
386
387 let defines_fields = public_defines
388 .keys()
389 .map(|k| Ident::new(k, Span::call_site()))
390 .collect::<Vec<_>>();
391 let defines_values = public_defines.values();
392 let defines_struct = quote!(
393 struct ExpandedDefines {
394 #(#defines_fields: i32,)*
395 }
396 );
397 let defines_init = quote!(
398 ExpandedDefines {
399 #(#defines_fields: #defines_values,)*
400 }
401 );
402
403 let program_size = max_program_size;
404
405 let dummy_include = match file {
410 Some(file_path) => quote! {let _ = include_bytes!( #file_path );},
411 None => quote!(),
412 };
413 quote! {
414 {
415 #defines_struct
416 {
417 #dummy_include;
418 #krate::ProgramWithDefines {
419 program: #krate::Program::<{ #program_size }> {
420 code: #code,
421 origin: #origin,
422 wrap: #wrap,
423 side_set: #side_set,
424 version: #version,
425 },
426 public_defines: #defines_init,
427 }
428 }
429 }
430 }
431}
432
433fn parse_error(error: &pio_parser::ParseError, program_source: &str) -> proc_macro2::TokenStream {
434 let e = error;
435 let files = codespan_reporting::files::SimpleFile::new("source", program_source);
436
437 let (loc, messages) = match e {
438 ParseError::InvalidToken { location } => {
439 (*location..*location, vec!["invalid token".to_string()])
440 }
441 ParseError::UnrecognizedEof { location, expected } => (
442 *location..*location,
443 vec![
444 "unrecognized eof".to_string(),
445 format!("expected one of {}", expected.join(", ")),
446 ],
447 ),
448 ParseError::UnrecognizedToken { token, expected } => (
449 token.0..token.2,
450 vec![
451 format!("unexpected token: {:?}", format!("{}", token.1)),
452 format!("expected one of {}", expected.join(", ")),
453 ],
454 ),
455 ParseError::ExtraToken { token } => {
456 (token.0..token.2, vec![format!("extra token: {}", token.1)])
457 }
458 ParseError::User { error } => (0..0, vec![error.to_string()]),
459 };
460
461 let diagnostic = codespan_reporting::diagnostic::Diagnostic::error()
462 .with_message(messages[0].clone())
463 .with_labels(
464 messages
465 .iter()
466 .enumerate()
467 .map(|(i, m)| {
468 codespan_reporting::diagnostic::Label::new(
469 if i == 0 {
470 codespan_reporting::diagnostic::LabelStyle::Primary
471 } else {
472 codespan_reporting::diagnostic::LabelStyle::Secondary
473 },
474 (),
475 loc.clone(),
476 )
477 .with_message(m)
478 })
479 .collect(),
480 );
481
482 let mut writer = codespan_reporting::term::termcolor::Buffer::ansi();
483 let config = codespan_reporting::term::Config::default();
484 codespan_reporting::term::emit(&mut writer, &config, &files, &diagnostic).unwrap();
485 let data = writer.into_inner();
486 let data = std::str::from_utf8(&data).unwrap();
487
488 quote! {
489 compile_error!(#data)
490 }
491}