Skip to content

Commit 63b05cb

Browse files
kulpemilio
authored andcommitted
Generate func_macro callbacks
1 parent db672a3 commit 63b05cb

File tree

3 files changed

+107
-29
lines changed

3 files changed

+107
-29
lines changed

src/callbacks.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,8 @@ pub trait ParseCallbacks: fmt::Debug + UnwindSafe {
4545
///
4646
/// The first parameter represents the name and argument list (including the
4747
/// parentheses) of the function-like macro. The second parameter represents
48-
/// the expansion of the macro. It is not guaranteed that the whitespace of
49-
/// the original is preserved, but it is guaranteed that tokenization will
50-
/// not be changed.
51-
fn func_macro(&self, _name: &str, _value: &str) {}
48+
/// the expansion of the macro as a sequence of tokens.
49+
fn func_macro(&self, _name: &str, _value: &[&[u8]]) {}
5250

5351
/// This function should return whether, given an enum variant
5452
/// name, and value, this enum variant will forcibly be a constant.

src/clang.rs

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -709,30 +709,9 @@ impl Cursor {
709709

710710
/// Gets the tokens that correspond to that cursor as `cexpr` tokens.
711711
pub fn cexpr_tokens(self) -> Vec<cexpr::token::Token> {
712-
use cexpr::token;
713-
714712
self.tokens()
715713
.iter()
716-
.filter_map(|token| {
717-
let kind = match token.kind {
718-
CXToken_Punctuation => token::Kind::Punctuation,
719-
CXToken_Literal => token::Kind::Literal,
720-
CXToken_Identifier => token::Kind::Identifier,
721-
CXToken_Keyword => token::Kind::Keyword,
722-
// NB: cexpr is not too happy about comments inside
723-
// expressions, so we strip them down here.
724-
CXToken_Comment => return None,
725-
_ => {
726-
error!("Found unexpected token kind: {:?}", token);
727-
return None;
728-
}
729-
};
730-
731-
Some(token::Token {
732-
kind,
733-
raw: token.spelling().to_vec().into_boxed_slice(),
734-
})
735-
})
714+
.filter_map(|token| token.as_cexpr_token())
736715
.collect()
737716
}
738717

@@ -826,6 +805,30 @@ impl ClangToken {
826805
};
827806
c_str.to_bytes()
828807
}
808+
809+
/// Converts a ClangToken to a `cexpr` token if possible.
810+
pub fn as_cexpr_token(&self) -> Option<cexpr::token::Token> {
811+
use cexpr::token;
812+
813+
let kind = match self.kind {
814+
CXToken_Punctuation => token::Kind::Punctuation,
815+
CXToken_Literal => token::Kind::Literal,
816+
CXToken_Identifier => token::Kind::Identifier,
817+
CXToken_Keyword => token::Kind::Keyword,
818+
// NB: cexpr is not too happy about comments inside
819+
// expressions, so we strip them down here.
820+
CXToken_Comment => return None,
821+
_ => {
822+
error!("Found unexpected token kind: {:?}", self);
823+
return None;
824+
}
825+
};
826+
827+
Some(token::Token {
828+
kind,
829+
raw: self.spelling().to_vec().into_boxed_slice(),
830+
})
831+
}
829832
}
830833

831834
impl Drop for ClangToken {

src/ir/var.rs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::item::Item;
88
use super::ty::{FloatKind, TypeKind};
99
use crate::callbacks::MacroParsingBehavior;
1010
use crate::clang;
11+
use crate::clang::ClangToken;
1112
use crate::parse::{
1213
ClangItemParser, ClangSubItemParser, ParseError, ParseResult,
1314
};
@@ -130,6 +131,75 @@ fn default_macro_constant_type(value: i64) -> IntKind {
130131
}
131132
}
132133

134+
/// Determines whether a set of tokens from a CXCursor_MacroDefinition
135+
/// represent a function-like macro. If so, calls the func_macro callback
136+
/// and returns `Err(ParseError::Continue)` to signal to skip further
137+
/// processing. If conversion to UTF-8 fails (it is performed only where it
138+
/// should be infallible), then `Err(ParseError::Continue)` is returned as well.
139+
fn handle_function_macro(
140+
cursor: &clang::Cursor,
141+
tokens: &[ClangToken],
142+
callbacks: &dyn crate::callbacks::ParseCallbacks,
143+
) -> Result<(), ParseError> {
144+
fn is_abutting(a: &ClangToken, b: &ClangToken) -> bool {
145+
unsafe {
146+
clang_sys::clang_equalLocations(
147+
clang_sys::clang_getRangeEnd(a.extent),
148+
clang_sys::clang_getRangeStart(b.extent),
149+
) != 0
150+
}
151+
}
152+
153+
let is_functional_macro =
154+
// If we have libclang >= 3.9, we can use `is_macro_function_like()` and
155+
// avoid checking for abutting tokens ourselves.
156+
cursor.is_macro_function_like().unwrap_or_else(|| {
157+
// If we cannot get a definitive answer from clang, we instead check
158+
// for a parenthesis token immediately adjacent to (that is,
159+
// abutting) the first token in the macro definition.
160+
// TODO: Once we don't need the fallback check here, we can hoist
161+
// the `is_macro_function_like` check into this function's caller,
162+
// and thus avoid allocating the `tokens` vector for non-functional
163+
// macros.
164+
match tokens.get(0..2) {
165+
Some([a, b]) => is_abutting(&a, &b) && b.spelling() == b"(",
166+
_ => false,
167+
}
168+
});
169+
170+
if !is_functional_macro {
171+
return Ok(());
172+
}
173+
174+
let is_closing_paren = |t: &ClangToken| {
175+
// Test cheap token kind before comparing exact spellings.
176+
t.kind == clang_sys::CXToken_Punctuation && t.spelling() == b")"
177+
};
178+
let boundary = tokens.iter().position(is_closing_paren);
179+
180+
let mut spelled = tokens.iter().map(ClangToken::spelling);
181+
// Add 1, to convert index to length.
182+
let left = spelled
183+
.by_ref()
184+
.take(boundary.ok_or(ParseError::Continue)? + 1);
185+
let left = left.collect::<Vec<_>>().concat();
186+
let left = String::from_utf8(left).map_err(|_| ParseError::Continue)?;
187+
let right = spelled;
188+
// Drop last token with LLVM < 4.0, due to an LLVM bug.
189+
//
190+
// See:
191+
// https://bugs.llvm.org//show_bug.cgi?id=9069
192+
let len = match (right.len(), crate::clang_version().parsed) {
193+
(len, Some((v, _))) if len > 0 && v < 4 => len - 1,
194+
(len, _) => len,
195+
};
196+
let right: Vec<_> = right.take(len).collect();
197+
callbacks.func_macro(&left, &right);
198+
199+
// We handled the macro, skip future macro processing.
200+
Err(ParseError::Continue)
201+
}
202+
133203
impl ClangSubItemParser for Var {
134204
fn parse(
135205
cursor: clang::Cursor,
@@ -140,16 +210,20 @@ impl ClangSubItemParser for Var {
140210
use clang_sys::*;
141211
match cursor.kind() {
142212
CXCursor_MacroDefinition => {
213+
let tokens: Vec<_> = cursor.tokens().iter().collect();
214+
143215
if let Some(callbacks) = ctx.parse_callbacks() {
144216
match callbacks.will_parse_macro(&cursor.spelling()) {
145217
MacroParsingBehavior::Ignore => {
146218
return Err(ParseError::Continue);
147219
}
148220
MacroParsingBehavior::Default => {}
149221
}
222+
223+
handle_function_macro(&cursor, &tokens, callbacks)?;
150224
}
151225

152-
let value = parse_macro(ctx, &cursor);
226+
let value = parse_macro(ctx, &tokens);
153227

154228
let (id, value) = match value {
155229
Some(v) => v,
@@ -316,11 +390,14 @@ impl ClangSubItemParser for Var {
316390
/// Try and parse a macro using all the macros parsed until now.
317391
fn parse_macro(
318392
ctx: &BindgenContext,
319-
cursor: &clang::Cursor,
393+
tokens: &[ClangToken],
320394
) -> Option<(Vec<u8>, cexpr::expr::EvalResult)> {
321395
use cexpr::expr;
322396

323-
let mut cexpr_tokens = cursor.cexpr_tokens();
397+
let mut cexpr_tokens: Vec<_> = tokens
398+
.iter()
399+
.filter_map(ClangToken::as_cexpr_token)
400+
.collect();
324401

325402
let parser = expr::IdentifierParser::new(ctx.parsed_macros());
326403

0 commit comments

Comments
 (0)