From 295d153b3f6fb21dafb098a04c9cbbe3b3acf53d Mon Sep 17 00:00:00 2001 From: Aidan Hobson Sayers Date: Wed, 7 Jan 2015 02:55:14 +0000 Subject: Macro is now a reserved keyword --- src/bgmacro.rs | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 +- src/macro.rs | 323 --------------------------------------------------------- 3 files changed, 325 insertions(+), 325 deletions(-) create mode 100644 src/bgmacro.rs delete mode 100644 src/macro.rs diff --git a/src/bgmacro.rs b/src/bgmacro.rs new file mode 100644 index 00000000..d4bc58b4 --- /dev/null +++ b/src/bgmacro.rs @@ -0,0 +1,323 @@ +use std::default::Default; +use std::os; + +use syntax::ast; +use syntax::codemap; +use syntax::ext::base; +use syntax::fold::Folder; +use syntax::parse::token; +use syntax::ptr::P; +use syntax::util::small_vector::SmallVector; + +use super::{Bindings, BindgenOptions, LinkType, Logger}; + +pub fn bindgen_macro(cx: &mut base::ExtCtxt, sp: codemap::Span, tts: &[ast::TokenTree]) -> Box { + let mut visit = BindgenArgsVisitor { + options: Default::default(), + seen_named: false + }; + + visit.options.builtins = true; + if !parse_macro_opts(cx, tts, &mut visit) { + return base::DummyResult::any(sp); + } + + // Reparse clang_args as it is passed in string form + let clang_args = visit.options.clang_args.connect(" "); + visit.options.clang_args = parse_process_args(clang_args.as_slice()); + + // Set the working dir to the directory containing the invoking rs file so + // that clang searches for headers relative to it rather than the crate root + let mod_dir = Path::new(cx.codemap().span_to_filename(sp)).dirname().to_vec(); + let cwd = match os::getcwd() { + Ok(d) => d, + Err(e) => panic!("Invalid current working directory: {}", e), + }; + let p = Path::new(mod_dir); + if let Err(e) = os::change_dir(&p) { + panic!("Failed to change to directory {}: {}", p.display(), e); + }; + + // We want the span for errors to just match the bindgen! symbol + // instead of the whole invocation which can span multiple lines + let mut short_span = sp; + short_span.hi = short_span.lo + codemap::BytePos(8); + + let logger = MacroLogger { sp: short_span, cx: cx }; + + let ret = match Bindings::generate(&visit.options, Some(&logger as &Logger), Some(short_span)) { + Ok(bindings) => { + box BindgenResult { items: Some(SmallVector::many(bindings.into_ast())) } as Box + } + Err(_) => base::DummyResult::any(sp) + }; + + let p = Path::new(cwd); + if let Err(e) = os::change_dir(&p) { + panic!("Failed to return to directory {}: {}", p.display(), e); + } + + ret +} + +trait MacroArgsVisitor { + fn visit_str(&mut self, name: Option<&str>, val: &str) -> bool; + fn visit_int(&mut self, name: Option<&str>, val: i64) -> bool; + fn visit_bool(&mut self, name: Option<&str>, val: bool) -> bool; + fn visit_ident(&mut self, name: Option<&str>, ident: &str) -> bool; +} + +struct BindgenArgsVisitor { + pub options: BindgenOptions, + seen_named: bool +} + +impl MacroArgsVisitor for BindgenArgsVisitor { + fn visit_str(&mut self, mut name: Option<&str>, val: &str) -> bool { + if name.is_some() { self.seen_named = true; } + else if !self.seen_named { name = Some("clang_args") } + match name { + Some("link") => self.options.links.push((val.to_string(), LinkType::Default)), + Some("link_static") => self.options.links.push((val.to_string(), LinkType::Static)), + Some("link_framework") => self.options.links.push((val.to_string(), LinkType::Framework)), + Some("match") => self.options.match_pat.push(val.to_string()), + Some("clang_args") => self.options.clang_args.push(val.to_string()), + Some("enum_type") => self.options.override_enum_ty = val.to_string(), + _ => return false + } + true + } + + fn visit_int(&mut self, name: Option<&str>, _val: i64) -> bool { + if name.is_some() { self.seen_named = true; } + false + } + + fn visit_bool(&mut self, name: Option<&str>, val: bool) -> bool { + if name.is_some() { self.seen_named = true; } + match name { + Some("allow_unknown_types") => self.options.fail_on_unknown_type = !val, + Some("emit_builtins") => self.options.builtins = val, + _ => return false + } + true + } + + fn visit_ident(&mut self, name: Option<&str>, _val: &str) -> bool { + if name.is_some() { self.seen_named = true; } + false + } +} + +// Parses macro invocations in the form [ident=|:]value where value is an ident or literal +// e.g. bindgen!(module_name, "header.h", emit_builtins=false, clang_args:"-I /usr/local/include") +fn parse_macro_opts(cx: &mut base::ExtCtxt, tts: &[ast::TokenTree], visit: &mut MacroArgsVisitor) -> bool { + let mut parser = cx.new_parser_from_tts(tts); + let mut args_good = true; + + loop { + let mut name: Option = None; + let mut span = parser.span; + + // Check for [ident=]value and if found save ident to name + if parser.look_ahead(1, |t| t == &token::Eq) { + match parser.bump_and_get() { + token::Ident(ident, _) => { + let ident = parser.id_to_interned_str(ident); + name = Some(ident.get().to_string()); + parser.expect(&token::Eq); + }, + _ => { + cx.span_err(span, "invalid argument format"); + return false + } + } + } + + match parser.token { + // Match [ident] + token::Ident(val, _) => { + let val = parser.id_to_interned_str(val); + span.hi = parser.span.hi; + parser.bump(); + + // Bools are simply encoded as idents + let ret = match val.get() { + "true" => visit.visit_bool(as_str(&name), true), + "false" => visit.visit_bool(as_str(&name), false), + val => visit.visit_ident(as_str(&name), val) + }; + if !ret { + cx.span_err(span, "invalid argument"); + args_good = false; + } + } + // Match [literal] and parse as an expression so we can expand macros + _ => { + let expr = cx.expander().fold_expr(parser.parse_expr()); + span.hi = expr.span.hi; + match expr.node { + ast::ExprLit(ref lit) => { + let ret = match lit.node { + ast::LitStr(ref s, _) => visit.visit_str(as_str(&name), s.get()), + ast::LitBool(b) => visit.visit_bool(as_str(&name), b), + ast::LitInt(i, ast::SignedIntLit(_, sign)) | + ast::LitInt(i, ast::UnsuffixedIntLit(sign)) => { + let i = i as i64; + let i = if sign == ast::Minus { -i } else { i }; + visit.visit_int(as_str(&name), i) + }, + ast::LitInt(i, ast::UnsignedIntLit(_)) => visit.visit_int(as_str(&name), i as i64), + _ => { + cx.span_err(span, "invalid argument format"); + return false + } + }; + if !ret { + cx.span_err(span, "invalid argument"); + args_good = false; + } + }, + _ => { + cx.span_err(span, "invalid argument format"); + return false + } + } + } + } + + if parser.eat(&token::Eof) { + return args_good + } + + if !parser.eat(&token::Comma) { + cx.span_err(parser.span, "invalid argument format"); + return false + } + } +} + +// I'm sure there's a nicer way of doing it +fn as_str<'a>(owned: &'a Option) -> Option<&'a str> { + match owned { + &Some(ref s) => Some(s.as_slice()), + &None => None + } +} + +#[derive(PartialEq, Eq)] +enum QuoteState { + InNone, + InSingleQuotes, + InDoubleQuotes +} + +fn parse_process_args(s: &str) -> Vec { + let s = s.trim(); + let mut parts = Vec::new(); + let mut quote_state = QuoteState::InNone; + let mut positions = vec!(0); + let mut last = ' '; + for (i, c) in s.chars().chain(" ".chars()).enumerate() { + match (last, c) { + // Match \" set has_escaped and skip + ('\\', '\"') => (), + // Match \' + ('\\', '\'') => (), + // Match \ + // Check we don't escape the final added space + ('\\', ' ') if i < s.len() => (), + // Match \\ + ('\\', '\\') => (), + // Match " + (_, '\"') if quote_state == QuoteState::InNone => { + quote_state = QuoteState::InDoubleQuotes; + positions.push(i); + positions.push(i + 1); + }, + (_, '\"') if quote_state == QuoteState::InDoubleQuotes => { + quote_state = QuoteState::InNone; + positions.push(i); + positions.push(i + 1); + }, + // Match ' + (_, '\'') if quote_state == QuoteState::InNone => { + quote_state = QuoteState::InSingleQuotes; + positions.push(i); + positions.push(i + 1); + }, + (_, '\'') if quote_state == QuoteState::InSingleQuotes => { + quote_state = QuoteState::InNone; + positions.push(i); + positions.push(i + 1); + }, + // Match + // If we are at the end of the string close any open quotes + (_, ' ') if quote_state == QuoteState::InNone || i >= s.len() => { + { + positions.push(i); + + let starts = positions.iter().enumerate().filter(|&(i, _)| i % 2 == 0); + let ends = positions.iter().enumerate().filter(|&(i, _)| i % 2 == 1); + + let part: Vec = starts.zip(ends).map(|((_, start), (_, end))| s.slice(*start, *end).to_string()).collect(); + + let part = part.connect(""); + + if part.len() > 0 { + // Remove any extra whitespace outside the quotes + let part = part.as_slice().trim(); + // Replace quoted characters + let part = part.replace("\\\"", "\""); + let part = part.replace("\\\'", "\'"); + let part = part.replace("\\ ", " "); + let part = part.replace("\\\\", "\\"); + parts.push(part); + } + } + + positions.clear(); + positions.push(i + 1); + }, + (_, _) => () + } + last = c; + } + parts +} + +struct MacroLogger<'a, 'b:'a> { + sp: codemap::Span, + cx: &'a base::ExtCtxt<'b> +} + +impl<'a, 'b> Logger for MacroLogger<'a, 'b> { + fn error(&self, msg: &str) { + self.cx.span_err(self.sp, msg) + } + + fn warn(&self, msg: &str) { + self.cx.span_warn(self.sp, msg) + } +} + +struct BindgenResult { + items: Option>> +} + +impl base::MacResult for BindgenResult { + fn make_items(mut self: Box) -> Option>> { + self.items.take() + } +} + +#[test] +fn test_parse_process_args() { + assert_eq!(parse_process_args("a b c"), vec!("a", "b", "c")); + assert_eq!(parse_process_args("a \"b\" c"), vec!("a", "b", "c")); + assert_eq!(parse_process_args("a \'b\' c"), vec!("a", "b", "c")); + assert_eq!(parse_process_args("a \"b c\""), vec!("a", "b c")); + assert_eq!(parse_process_args("a \'\"b\"\' c"), vec!("a", "\"b\"", "c")); + assert_eq!(parse_process_args("a b\\ c"), vec!("a", "b c")); + assert_eq!(parse_process_args("a b c\\"), vec!("a", "b", "c\\")); +} diff --git a/src/lib.rs b/src/lib.rs index 04ab19f0..32f2f57e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,12 +26,12 @@ mod clangll; mod clang; mod gen; mod parser; -mod macro; +mod bgmacro; #[doc(hidden)] #[plugin_registrar] pub fn plugin_registrar(reg: &mut Registry) { - reg.register_macro("bindgen", macro::bindgen_macro); + reg.register_macro("bindgen", bgmacro::bindgen_macro); } pub struct BindgenOptions { diff --git a/src/macro.rs b/src/macro.rs deleted file mode 100644 index d4bc58b4..00000000 --- a/src/macro.rs +++ /dev/null @@ -1,323 +0,0 @@ -use std::default::Default; -use std::os; - -use syntax::ast; -use syntax::codemap; -use syntax::ext::base; -use syntax::fold::Folder; -use syntax::parse::token; -use syntax::ptr::P; -use syntax::util::small_vector::SmallVector; - -use super::{Bindings, BindgenOptions, LinkType, Logger}; - -pub fn bindgen_macro(cx: &mut base::ExtCtxt, sp: codemap::Span, tts: &[ast::TokenTree]) -> Box { - let mut visit = BindgenArgsVisitor { - options: Default::default(), - seen_named: false - }; - - visit.options.builtins = true; - if !parse_macro_opts(cx, tts, &mut visit) { - return base::DummyResult::any(sp); - } - - // Reparse clang_args as it is passed in string form - let clang_args = visit.options.clang_args.connect(" "); - visit.options.clang_args = parse_process_args(clang_args.as_slice()); - - // Set the working dir to the directory containing the invoking rs file so - // that clang searches for headers relative to it rather than the crate root - let mod_dir = Path::new(cx.codemap().span_to_filename(sp)).dirname().to_vec(); - let cwd = match os::getcwd() { - Ok(d) => d, - Err(e) => panic!("Invalid current working directory: {}", e), - }; - let p = Path::new(mod_dir); - if let Err(e) = os::change_dir(&p) { - panic!("Failed to change to directory {}: {}", p.display(), e); - }; - - // We want the span for errors to just match the bindgen! symbol - // instead of the whole invocation which can span multiple lines - let mut short_span = sp; - short_span.hi = short_span.lo + codemap::BytePos(8); - - let logger = MacroLogger { sp: short_span, cx: cx }; - - let ret = match Bindings::generate(&visit.options, Some(&logger as &Logger), Some(short_span)) { - Ok(bindings) => { - box BindgenResult { items: Some(SmallVector::many(bindings.into_ast())) } as Box - } - Err(_) => base::DummyResult::any(sp) - }; - - let p = Path::new(cwd); - if let Err(e) = os::change_dir(&p) { - panic!("Failed to return to directory {}: {}", p.display(), e); - } - - ret -} - -trait MacroArgsVisitor { - fn visit_str(&mut self, name: Option<&str>, val: &str) -> bool; - fn visit_int(&mut self, name: Option<&str>, val: i64) -> bool; - fn visit_bool(&mut self, name: Option<&str>, val: bool) -> bool; - fn visit_ident(&mut self, name: Option<&str>, ident: &str) -> bool; -} - -struct BindgenArgsVisitor { - pub options: BindgenOptions, - seen_named: bool -} - -impl MacroArgsVisitor for BindgenArgsVisitor { - fn visit_str(&mut self, mut name: Option<&str>, val: &str) -> bool { - if name.is_some() { self.seen_named = true; } - else if !self.seen_named { name = Some("clang_args") } - match name { - Some("link") => self.options.links.push((val.to_string(), LinkType::Default)), - Some("link_static") => self.options.links.push((val.to_string(), LinkType::Static)), - Some("link_framework") => self.options.links.push((val.to_string(), LinkType::Framework)), - Some("match") => self.options.match_pat.push(val.to_string()), - Some("clang_args") => self.options.clang_args.push(val.to_string()), - Some("enum_type") => self.options.override_enum_ty = val.to_string(), - _ => return false - } - true - } - - fn visit_int(&mut self, name: Option<&str>, _val: i64) -> bool { - if name.is_some() { self.seen_named = true; } - false - } - - fn visit_bool(&mut self, name: Option<&str>, val: bool) -> bool { - if name.is_some() { self.seen_named = true; } - match name { - Some("allow_unknown_types") => self.options.fail_on_unknown_type = !val, - Some("emit_builtins") => self.options.builtins = val, - _ => return false - } - true - } - - fn visit_ident(&mut self, name: Option<&str>, _val: &str) -> bool { - if name.is_some() { self.seen_named = true; } - false - } -} - -// Parses macro invocations in the form [ident=|:]value where value is an ident or literal -// e.g. bindgen!(module_name, "header.h", emit_builtins=false, clang_args:"-I /usr/local/include") -fn parse_macro_opts(cx: &mut base::ExtCtxt, tts: &[ast::TokenTree], visit: &mut MacroArgsVisitor) -> bool { - let mut parser = cx.new_parser_from_tts(tts); - let mut args_good = true; - - loop { - let mut name: Option = None; - let mut span = parser.span; - - // Check for [ident=]value and if found save ident to name - if parser.look_ahead(1, |t| t == &token::Eq) { - match parser.bump_and_get() { - token::Ident(ident, _) => { - let ident = parser.id_to_interned_str(ident); - name = Some(ident.get().to_string()); - parser.expect(&token::Eq); - }, - _ => { - cx.span_err(span, "invalid argument format"); - return false - } - } - } - - match parser.token { - // Match [ident] - token::Ident(val, _) => { - let val = parser.id_to_interned_str(val); - span.hi = parser.span.hi; - parser.bump(); - - // Bools are simply encoded as idents - let ret = match val.get() { - "true" => visit.visit_bool(as_str(&name), true), - "false" => visit.visit_bool(as_str(&name), false), - val => visit.visit_ident(as_str(&name), val) - }; - if !ret { - cx.span_err(span, "invalid argument"); - args_good = false; - } - } - // Match [literal] and parse as an expression so we can expand macros - _ => { - let expr = cx.expander().fold_expr(parser.parse_expr()); - span.hi = expr.span.hi; - match expr.node { - ast::ExprLit(ref lit) => { - let ret = match lit.node { - ast::LitStr(ref s, _) => visit.visit_str(as_str(&name), s.get()), - ast::LitBool(b) => visit.visit_bool(as_str(&name), b), - ast::LitInt(i, ast::SignedIntLit(_, sign)) | - ast::LitInt(i, ast::UnsuffixedIntLit(sign)) => { - let i = i as i64; - let i = if sign == ast::Minus { -i } else { i }; - visit.visit_int(as_str(&name), i) - }, - ast::LitInt(i, ast::UnsignedIntLit(_)) => visit.visit_int(as_str(&name), i as i64), - _ => { - cx.span_err(span, "invalid argument format"); - return false - } - }; - if !ret { - cx.span_err(span, "invalid argument"); - args_good = false; - } - }, - _ => { - cx.span_err(span, "invalid argument format"); - return false - } - } - } - } - - if parser.eat(&token::Eof) { - return args_good - } - - if !parser.eat(&token::Comma) { - cx.span_err(parser.span, "invalid argument format"); - return false - } - } -} - -// I'm sure there's a nicer way of doing it -fn as_str<'a>(owned: &'a Option) -> Option<&'a str> { - match owned { - &Some(ref s) => Some(s.as_slice()), - &None => None - } -} - -#[derive(PartialEq, Eq)] -enum QuoteState { - InNone, - InSingleQuotes, - InDoubleQuotes -} - -fn parse_process_args(s: &str) -> Vec { - let s = s.trim(); - let mut parts = Vec::new(); - let mut quote_state = QuoteState::InNone; - let mut positions = vec!(0); - let mut last = ' '; - for (i, c) in s.chars().chain(" ".chars()).enumerate() { - match (last, c) { - // Match \" set has_escaped and skip - ('\\', '\"') => (), - // Match \' - ('\\', '\'') => (), - // Match \ - // Check we don't escape the final added space - ('\\', ' ') if i < s.len() => (), - // Match \\ - ('\\', '\\') => (), - // Match " - (_, '\"') if quote_state == QuoteState::InNone => { - quote_state = QuoteState::InDoubleQuotes; - positions.push(i); - positions.push(i + 1); - }, - (_, '\"') if quote_state == QuoteState::InDoubleQuotes => { - quote_state = QuoteState::InNone; - positions.push(i); - positions.push(i + 1); - }, - // Match ' - (_, '\'') if quote_state == QuoteState::InNone => { - quote_state = QuoteState::InSingleQuotes; - positions.push(i); - positions.push(i + 1); - }, - (_, '\'') if quote_state == QuoteState::InSingleQuotes => { - quote_state = QuoteState::InNone; - positions.push(i); - positions.push(i + 1); - }, - // Match - // If we are at the end of the string close any open quotes - (_, ' ') if quote_state == QuoteState::InNone || i >= s.len() => { - { - positions.push(i); - - let starts = positions.iter().enumerate().filter(|&(i, _)| i % 2 == 0); - let ends = positions.iter().enumerate().filter(|&(i, _)| i % 2 == 1); - - let part: Vec = starts.zip(ends).map(|((_, start), (_, end))| s.slice(*start, *end).to_string()).collect(); - - let part = part.connect(""); - - if part.len() > 0 { - // Remove any extra whitespace outside the quotes - let part = part.as_slice().trim(); - // Replace quoted characters - let part = part.replace("\\\"", "\""); - let part = part.replace("\\\'", "\'"); - let part = part.replace("\\ ", " "); - let part = part.replace("\\\\", "\\"); - parts.push(part); - } - } - - positions.clear(); - positions.push(i + 1); - }, - (_, _) => () - } - last = c; - } - parts -} - -struct MacroLogger<'a, 'b:'a> { - sp: codemap::Span, - cx: &'a base::ExtCtxt<'b> -} - -impl<'a, 'b> Logger for MacroLogger<'a, 'b> { - fn error(&self, msg: &str) { - self.cx.span_err(self.sp, msg) - } - - fn warn(&self, msg: &str) { - self.cx.span_warn(self.sp, msg) - } -} - -struct BindgenResult { - items: Option>> -} - -impl base::MacResult for BindgenResult { - fn make_items(mut self: Box) -> Option>> { - self.items.take() - } -} - -#[test] -fn test_parse_process_args() { - assert_eq!(parse_process_args("a b c"), vec!("a", "b", "c")); - assert_eq!(parse_process_args("a \"b\" c"), vec!("a", "b", "c")); - assert_eq!(parse_process_args("a \'b\' c"), vec!("a", "b", "c")); - assert_eq!(parse_process_args("a \"b c\""), vec!("a", "b c")); - assert_eq!(parse_process_args("a \'\"b\"\' c"), vec!("a", "\"b\"", "c")); - assert_eq!(parse_process_args("a b\\ c"), vec!("a", "b c")); - assert_eq!(parse_process_args("a b c\\"), vec!("a", "b", "c\\")); -} -- cgit v1.2.3