1use cpp_common::{Class, Closure, Macro, RustInvocation};
2use lazy_static::lazy_static;
3use regex::Regex;
4use std::fmt;
5use std::fs::File;
6use std::io::Read;
7use std::mem::swap;
8use std::path::{Path, PathBuf};
9use syn::visit::Visit;
10
11#[allow(clippy::enum_variant_names)]
12#[derive(Debug)]
13pub enum Error {
14 ParseCannotOpenFile { src_path: String },
15 ParseSyntaxError { src_path: String, error: syn::parse::Error },
16 LexError { src_path: String, line: u32 },
17}
18
19impl fmt::Display for Error {
20 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21 match self {
22 Error::ParseCannotOpenFile { ref src_path: &String } => {
23 write!(f, "Parsing crate: cannot open file `{}`.", src_path)
24 }
25 Error::ParseSyntaxError { ref src_path: &String, ref error: &Error } => {
26 write!(f, "Parsing file : `{}`:\n{}", src_path, error)
27 }
28 Error::LexError { ref src_path: &String, ref line: &u32 } => {
29 write!(f, "{}:{}: Lexing error", src_path, line + 1)
30 }
31 }
32 }
33}
34
35#[derive(Debug)]
36struct LineError(u32, String);
37
38impl LineError {
39 fn add_line(self, a: u32) -> LineError {
40 LineError(self.0 + a, self.1)
41 }
42}
43
44impl fmt::Display for LineError {
45 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46 write!(f, "{}:{}", self.0 + 1, self.1)
47 }
48}
49
50impl From<LexError> for LineError {
51 fn from(e: LexError) -> Self {
52 LineError(e.line, "Lexing error".into())
53 }
54}
55
56enum ExpandSubMacroType<'a> {
57 Lit,
58 Closure(&'a mut u32), // the offset
59}
60
61// Given a string containing some C++ code with a rust! macro,
62// this functions expand the rust! macro to a call to an extern
63// function
64fn expand_sub_rust_macro(input: String, mut t: ExpandSubMacroType) -> Result<String, LineError> {
65 let mut result = input;
66 let mut extra_decl = String::new();
67 let mut search_index = 0;
68
69 loop {
70 let (begin, end, line) = {
71 let mut begin = 0;
72 let mut cursor = new_cursor(&result);
73 cursor.advance(search_index);
74 while !cursor.is_empty() {
75 cursor = skip_whitespace(cursor);
76 let r = skip_literal(cursor)?;
77 cursor = r.0;
78 if r.1 {
79 continue;
80 }
81 if cursor.is_empty() {
82 break;
83 }
84 if let Ok((cur, ident)) = symbol(cursor) {
85 begin = cursor.off as usize;
86 cursor = cur;
87 if ident != "rust" {
88 continue;
89 }
90 } else {
91 cursor = cursor.advance(1);
92 continue;
93 }
94 cursor = skip_whitespace(cursor);
95 if !cursor.starts_with("!") {
96 continue;
97 }
98 break;
99 }
100 if cursor.is_empty() {
101 return Ok(extra_decl + &result);
102 }
103 let end = find_delimited((find_delimited(cursor, "(")?.0).advance(1), ")")?.0;
104 (begin, end.off as usize + 1, cursor.line)
105 };
106 let input: ::proc_macro2::TokenStream = result[begin..end]
107 .parse()
108 .map_err(|_| LineError(line, "TokenStream parse error".into()))?;
109 let rust_invocation =
110 ::syn::parse2::<RustInvocation>(input).map_err(|e| LineError(line, e.to_string()))?;
111 let fn_name = match t {
112 ExpandSubMacroType::Lit => {
113 extra_decl.push_str(&format!("extern \"C\" void {}();\n", rust_invocation.id));
114 rust_invocation.id.clone().to_string()
115 }
116 ExpandSubMacroType::Closure(ref mut offset) => {
117 use cpp_common::FILE_HASH;
118 **offset += 1;
119 format!(
120 "rust_cpp_callbacks{file_hash}[{offset}]",
121 file_hash = *FILE_HASH,
122 offset = **offset - 1
123 )
124 }
125 };
126
127 let mut decl_types = rust_invocation
128 .arguments
129 .iter()
130 .map(|(_, val)| format!("rustcpp::argument_helper<{}>::type", val))
131 .collect::<Vec<_>>();
132 let mut call_args =
133 rust_invocation.arguments.iter().map(|(val, _)| val.to_string()).collect::<Vec<_>>();
134
135 let fn_call = match rust_invocation.return_type {
136 None => format!(
137 "reinterpret_cast<void (*)({types})>({f})({args})",
138 f = fn_name,
139 types = decl_types.join(", "),
140 args = call_args.join(", ")
141 ),
142 Some(rty) => {
143 decl_types.push(format!("rustcpp::return_helper<{rty}>", rty = rty));
144 call_args.push("0".to_string());
145 format!(
146 "std::move(*reinterpret_cast<{rty}*(*)({types})>({f})({args}))",
147 rty = rty,
148 f = fn_name,
149 types = decl_types.join(", "),
150 args = call_args.join(", ")
151 )
152 }
153 };
154
155 let fn_call = {
156 // remove the rust! macro from the C++ snippet
157 let orig = result.drain(begin..end);
158 // add \ņ to the invocation in order to keep the same amount of line numbers
159 // so errors point to the right line.
160 orig.filter(|x| *x == '\n').fold(fn_call, |mut res, _| {
161 res.push('\n');
162 res
163 })
164 };
165 // add the invocation of call where the rust! macro used to be.
166 result.insert_str(begin, &fn_call);
167 search_index = begin + fn_call.len();
168 }
169}
170
171#[test]
172fn test_expand_sub_rust_macro() {
173 let x: Result = expand_sub_rust_macro(input:"{ rust!(xxx [] { 1 }); }".to_owned(), t:ExpandSubMacroType::Lit);
174 assert_eq!(x.unwrap(), "extern \"C\" void xxx();\n{ reinterpret_cast<void (*)()>(xxx)(); }");
175
176 let x: Result = expand_sub_rust_macro(
177 input:"{ hello( rust!(xxx [] { 1 }), rust!(yyy [] { 2 }); ) }".to_owned(),
178 t:ExpandSubMacroType::Lit,
179 );
180 assert_eq!(x.unwrap(), "extern \"C\" void xxx();\nextern \"C\" void yyy();\n{ hello( reinterpret_cast<void (*)()>(xxx)(), reinterpret_cast<void (*)()>(yyy)(); ) }");
181
182 let s: String = "{ /* rust! */ /* rust!(xxx [] { 1 }) */ }".to_owned();
183 assert_eq!(expand_sub_rust_macro(s.clone(), ExpandSubMacroType::Lit).unwrap(), s);
184}
185
186#[path = "strnom.rs"]
187mod strnom;
188use crate::strnom::*;
189
190fn skip_literal(mut input: Cursor) -> PResult<bool> {
191 //input = whitespace(input)?.0;
192 if input.starts_with("\"") {
193 input = cooked_string(input.advance(1))?.0;
194 debug_assert!(input.starts_with("\""));
195 return Ok((input.advance(1), true));
196 }
197 if input.starts_with("b\"") {
198 input = cooked_byte_string(input.advance(2))?.0;
199 debug_assert!(input.starts_with("\""));
200 return Ok((input.advance(1), true));
201 }
202 if input.starts_with("\'") {
203 input = input.advance(1);
204 let cur = cooked_char(input)?.0;
205 if !cur.starts_with("\'") {
206 return Ok((symbol(input)?.0, true));
207 }
208 return Ok((cur.advance(1), true));
209 }
210 if input.starts_with("b\'") {
211 input = cooked_byte(input.advance(2))?.0;
212 if !input.starts_with("\'") {
213 return Err(LexError { line: input.line });
214 }
215 return Ok((input.advance(1), true));
216 }
217 lazy_static! {
218 static ref RAW: Regex = Regex::new(r##"^b?r#*""##).unwrap();
219 }
220 if RAW.is_match(input.rest) {
221 let q = input.rest.find('r').unwrap();
222 input = input.advance(q + 1);
223 return raw_string(input).map(|x| (x.0, true));
224 }
225 Ok((input, false))
226}
227
228fn new_cursor(s: &str) -> Cursor {
229 Cursor { rest: s, off: 0, line: 0, column: 0 }
230}
231
232#[test]
233fn test_skip_literal() -> Result<(), LexError> {
234 assert!((skip_literal(new_cursor(r#""fofofo"ok xx"#))?.0).starts_with("ok"));
235 assert!((skip_literal(new_cursor(r#""kk\"kdk"ok xx"#))?.0).starts_with("ok"));
236 assert!((skip_literal(new_cursor("r###\"foo \" bar \\\" \"###ok xx"))?.0).starts_with("ok"));
237 assert!(
238 (skip_literal(new_cursor("br###\"foo 'jjk' \" bar \\\" \"###ok xx"))?.0).starts_with("ok")
239 );
240 assert!((skip_literal(new_cursor("'4'ok xx"))?.0).starts_with("ok"));
241 assert!((skip_literal(new_cursor("'\''ok xx"))?.0).starts_with("ok"));
242 assert!((skip_literal(new_cursor("b'\''ok xx"))?.0).starts_with("ok"));
243 assert!((skip_literal(new_cursor("'abc ok xx"))?.0).starts_with(" ok"));
244 assert!((skip_literal(new_cursor("'a ok xx"))?.0).starts_with(" ok"));
245
246 assert!((skip_whitespace(new_cursor("ok xx"))).starts_with("ok"));
247 assert!((skip_whitespace(new_cursor(" ok xx"))).starts_with("ok"));
248 assert!((skip_whitespace(new_cursor(" \n /* /*dd \n // */ */ // foo \n ok xx/* */")))
249 .starts_with("ok"));
250
251 Ok(())
252}
253
254// advance the cursor until it finds the needle.
255fn find_delimited<'a>(mut input: Cursor<'a>, needle: &str) -> PResult<'a, ()> {
256 let mut stack: Vec<&'static str> = vec![];
257 while !input.is_empty() {
258 input = skip_whitespace(input);
259 input = skip_literal(input)?.0;
260 if input.is_empty() {
261 break;
262 }
263 if stack.is_empty() && input.starts_with(needle) {
264 return Ok((input, ()));
265 } else if stack.last().map_or(false, |x| input.starts_with(x)) {
266 stack.pop();
267 } else if input.starts_with("(") {
268 stack.push(")");
269 } else if input.starts_with("[") {
270 stack.push("]");
271 } else if input.starts_with("{") {
272 stack.push("}");
273 } else if input.starts_with(")") || input.starts_with("]") || input.starts_with("}") {
274 return Err(LexError { line: input.line });
275 }
276 input = input.advance(1);
277 }
278 Err(LexError { line: input.line })
279}
280
281#[test]
282fn test_find_delimited() -> Result<(), LexError> {
283 assert!((find_delimited(new_cursor(" x f ok"), "f")?.0).starts_with("f ok"));
284 assert!((find_delimited(new_cursor(" {f} f ok"), "f")?.0).starts_with("f ok"));
285 assert!((find_delimited(new_cursor(" (f\")\" { ( ) } /* ) */ f ) f ok"), "f")?.0)
286 .starts_with("f ok"));
287 Ok(())
288}
289
290#[test]
291fn test_cursor_advance() -> Result<(), LexError> {
292 assert_eq!(new_cursor("\n\n\n").advance(2).line, 2);
293 assert_eq!(new_cursor("\n \n\n").advance(2).line, 1);
294 assert_eq!(new_cursor("\n\n\n").advance(2).column, 0);
295 assert_eq!(new_cursor("\n \n\n").advance(2).column, 1);
296
297 assert_eq!((find_delimited(new_cursor("\n/*\n \n */ ( \n ) /* */ f"), "f")?.0).line, 4);
298 assert_eq!((find_delimited(new_cursor("\n/*\n \n */ ( \n ) /* */ f"), "f")?.0).column, 9);
299 Ok(())
300}
301
302fn line_directive(path: &Path, cur: Cursor) -> String {
303 let mut line: String =
304 format!("#line {} \"{}\"\n", cur.line + 1, path.to_string_lossy().replace('\\', "\\\\"));
305 for _ in 0..cur.column {
306 line.push(ch:' ');
307 }
308 line
309}
310
311#[derive(Default)]
312pub struct Parser {
313 pub closures: Vec<Closure>,
314 pub classes: Vec<Class>,
315 pub snippets: String,
316 pub callbacks_count: u32,
317 current_path: PathBuf, // The current file being parsed
318 mod_dir: PathBuf,
319 mod_error: Option<Error>, // An error occuring while visiting the modules
320}
321
322impl Parser {
323 pub fn parse_crate(&mut self, crate_root: PathBuf) -> Result<(), Error> {
324 let parent = crate_root.parent().map(|x| x.to_owned()).unwrap_or_default();
325 self.parse_mod(crate_root, parent)
326 }
327
328 fn parse_mod(&mut self, mod_path: PathBuf, submod_dir: PathBuf) -> Result<(), Error> {
329 let mut s = String::new();
330 let mut f = File::open(&mod_path).map_err(|_| Error::ParseCannotOpenFile {
331 src_path: mod_path.to_str().unwrap().to_owned(),
332 })?;
333 f.read_to_string(&mut s).map_err(|_| Error::ParseCannotOpenFile {
334 src_path: mod_path.to_str().unwrap().to_owned(),
335 })?;
336
337 let fi = syn::parse_file(&s).map_err(|x| Error::ParseSyntaxError {
338 src_path: mod_path.to_str().unwrap().to_owned(),
339 error: x,
340 })?;
341
342 let mut current_path = mod_path;
343 let mut mod_dir = submod_dir;
344
345 swap(&mut self.current_path, &mut current_path);
346 swap(&mut self.mod_dir, &mut mod_dir);
347
348 self.find_cpp_macros(&s)?;
349 self.visit_file(&fi);
350 if let Some(err) = self.mod_error.take() {
351 return Err(err);
352 }
353
354 swap(&mut self.current_path, &mut current_path);
355 swap(&mut self.mod_dir, &mut mod_dir);
356
357 Ok(())
358 }
359
360 /*
361 fn parse_macro(&mut self, tts: TokenStream) {
362 let mut last_ident: Option<syn::Ident> = None;
363 let mut is_macro = false;
364 for t in tts.into_iter() {
365 match t {
366 TokenTree::Punct(ref p) if p.as_char() == '!' => is_macro = true,
367 TokenTree::Ident(i) => {
368 is_macro = false;
369 last_ident = Some(i);
370 }
371 TokenTree::Group(d) => {
372 if is_macro && last_ident.as_ref().map_or(false, |i| i == "cpp") {
373 self.handle_cpp(&d.stream())
374 } else if is_macro && last_ident.as_ref().map_or(false, |i| i == "cpp_class") {
375 self.handle_cpp_class(&d.stream())
376 } else {
377 self.parse_macro(d.stream())
378 }
379 is_macro = false;
380 last_ident = None;
381 }
382 _ => {
383 is_macro = false;
384 last_ident = None;
385 }
386 }
387 }
388 }
389 */
390
391 fn find_cpp_macros(&mut self, source: &str) -> Result<(), Error> {
392 let mut cursor = new_cursor(source);
393 while !cursor.is_empty() {
394 cursor = skip_whitespace(cursor);
395 let r = skip_literal(cursor).map_err(|e| self.lex_error(e))?;
396 cursor = r.0;
397 if r.1 {
398 continue;
399 }
400 if let Ok((cur, ident)) = symbol(cursor) {
401 cursor = cur;
402 if ident != "cpp" && ident != "cpp_class" {
403 continue;
404 }
405 cursor = skip_whitespace(cursor);
406 if !cursor.starts_with("!") {
407 continue;
408 }
409 cursor = skip_whitespace(cursor.advance(1));
410 let delim = if cursor.starts_with("(") {
411 ")"
412 } else if cursor.starts_with("[") {
413 "]"
414 } else if cursor.starts_with("{") {
415 "}"
416 } else {
417 continue;
418 };
419 cursor = cursor.advance(1);
420 let mut macro_cur = cursor;
421 cursor = find_delimited(cursor, delim).map_err(|e| self.lex_error(e))?.0;
422 let size = (cursor.off - macro_cur.off) as usize;
423 macro_cur.rest = &macro_cur.rest[..size];
424 if ident == "cpp" {
425 self.handle_cpp(macro_cur).unwrap_or_else(|e| {
426 panic!("Error while parsing cpp! macro:\n{:?}:{}", self.current_path, e)
427 });
428 } else {
429 debug_assert_eq!(ident, "cpp_class");
430 self.handle_cpp_class(macro_cur).unwrap_or_else(|e| {
431 panic!(
432 "Error while parsing cpp_class! macro:\n{:?}:{}",
433 self.current_path, e
434 )
435 });
436 }
437 continue;
438 }
439 if cursor.is_empty() {
440 break;
441 }
442 cursor = cursor.advance(1); // Not perfect, but should work
443 }
444 Ok(())
445 }
446
447 fn lex_error(&self, e: LexError) -> Error {
448 Error::LexError {
449 src_path: self.current_path.clone().to_str().unwrap().to_owned(),
450 line: e.line,
451 }
452 }
453
454 fn handle_cpp(&mut self, x: Cursor) -> Result<(), LineError> {
455 // Since syn don't give the exact string, we extract manually
456 let begin = (find_delimited(x, "{")?.0).advance(1);
457 let end = find_delimited(begin, "}")?.0;
458 let extracted = &begin.rest[..(end.off - begin.off) as usize];
459
460 let input: ::proc_macro2::TokenStream =
461 x.rest.parse().map_err(|_| LineError(x.line, "TokenStream parse error".into()))?;
462 match ::syn::parse2::<Macro>(input).map_err(|e| LineError(x.line, e.to_string()))? {
463 Macro::Closure(mut c) => {
464 c.callback_offset = self.callbacks_count;
465 c.body_str = line_directive(&self.current_path, begin)
466 + &expand_sub_rust_macro(
467 extracted.to_string(),
468 ExpandSubMacroType::Closure(&mut self.callbacks_count),
469 )
470 .map_err(|e| e.add_line(begin.line))?;
471 self.closures.push(c);
472 }
473 Macro::Lit(_l) => {
474 self.snippets.push('\n');
475 let snip = expand_sub_rust_macro(
476 line_directive(&self.current_path, begin) + extracted,
477 ExpandSubMacroType::Lit,
478 )
479 .map_err(|e| e.add_line(begin.line))?;
480 self.snippets.push_str(&snip);
481 }
482 }
483 Ok(())
484 }
485
486 fn handle_cpp_class(&mut self, x: Cursor) -> Result<(), LineError> {
487 let input: ::proc_macro2::TokenStream =
488 x.rest.parse().map_err(|_| LineError(x.line, "TokenStream parse error".into()))?;
489 let mut class =
490 ::syn::parse2::<Class>(input).map_err(|e| LineError(x.line, e.to_string()))?;
491 class.line = line_directive(&self.current_path, x);
492 self.classes.push(class);
493 Ok(())
494 }
495}
496
497impl<'ast> Visit<'ast> for Parser {
498 /* This is currently commented out because proc_macro2 don't allow us to get the text verbatim
499 (https://github.com/alexcrichton/proc-macro2/issues/110#issuecomment-411959999)
500 fn visit_macro(&mut self, mac: &syn::Macro) {
501 if mac.path.segments.len() != 1 {
502 return;
503 }
504 if mac.path.segments[0].ident == "cpp" {
505 self.handle_cpp(&mac.tts);
506 } else if mac.path.segments[0].ident == "cpp_class" {
507 self.handle_cpp_class(&mac.tts);
508 } else {
509 self.parse_macro(mac.tts.clone());
510 }
511 }*/
512
513 fn visit_item_mod(&mut self, item: &'ast syn::ItemMod) {
514 if self.mod_error.is_some() {
515 return;
516 }
517
518 if item.content.is_some() {
519 let mut parent = self.mod_dir.join(item.ident.to_string());
520 swap(&mut self.mod_dir, &mut parent);
521 syn::visit::visit_item_mod(self, item);
522 swap(&mut self.mod_dir, &mut parent);
523 return;
524 }
525
526 let mut cfg_disabled = false;
527
528 // Determine the path of the inner module's file
529 for attr in &item.attrs {
530 match &attr.meta {
531 // parse #[path = "foo.rs"]: read module from the specified path
532 syn::Meta::NameValue(syn::MetaNameValue {
533 path,
534 value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }),
535 ..
536 }) if path.is_ident("path") => {
537 let mod_path = self.mod_dir.join(s.value());
538 let parent = self.mod_dir.parent().map(|x| x.to_owned()).unwrap_or_default();
539 return self
540 .parse_mod(mod_path, parent)
541 .unwrap_or_else(|err| self.mod_error = Some(err));
542 }
543 // parse #[cfg(feature = "feature")]: don't follow modules not enabled by current features
544 syn::Meta::List(list @ syn::MetaList { path, .. }) if path.is_ident("cfg") => {
545 drop(list.parse_nested_meta(|meta| {
546 if meta.path.is_ident("feature") {
547 let feature: syn::LitStr = meta.value()?.parse()?;
548 let feature_env_var = "CARGO_FEATURE_".to_owned()
549 + &feature.value().to_uppercase().replace('-', "_");
550 if std::env::var_os(feature_env_var).is_none() {
551 cfg_disabled = true;
552 }
553 }
554 Ok(())
555 }))
556 }
557 _ => {}
558 }
559 }
560
561 if cfg_disabled {
562 return;
563 }
564
565 let mod_name = item.ident.to_string();
566 let subdir = self.mod_dir.join(&mod_name);
567 let subdir_mod = subdir.join("mod.rs");
568 if subdir_mod.is_file() {
569 return self
570 .parse_mod(subdir_mod, subdir)
571 .unwrap_or_else(|err| self.mod_error = Some(err));
572 }
573
574 let adjacent = self.mod_dir.join(format!("{}.rs", mod_name));
575 if adjacent.is_file() {
576 return self
577 .parse_mod(adjacent, subdir)
578 .unwrap_or_else(|err| self.mod_error = Some(err));
579 }
580
581 panic!(
582 "No file with module definition for `mod {}` in file {:?}",
583 mod_name, self.current_path
584 );
585 }
586}
587