Skip to content

Commit bc15759

Browse files
committed
Intelligently convert C/C++ comments to Rust
With this change, we can correctly parse C++ block comments. ``` /** * Does a thing * * More documentation. This test does something * useful. */ ``` into ``` /// Does a thing /// /// More documentation. This test does something /// useful. ``` Fixes rust-lang#426.
1 parent 3bb248b commit bc15759

30 files changed

+735
-458
lines changed

src/ir/comment.rs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
//! Utilities for manipulating C/C++ comments.
2+
3+
/// The type of a comment.
4+
#[derive(Debug, PartialEq, Eq)]
5+
enum Kind {
6+
/// A `///` comment, or something of the like.
7+
/// All lines in a comment should start with the same symbol.
8+
SingleLines,
9+
/// A `/**` comment, where each other line can start with `*` and the
10+
/// entire block ends with `*/`.
11+
MultiLine,
12+
}
13+
/// Preprocesses a C/C++ comment so that it is a valid Rust comment.
14+
pub fn preprocess(comment: String) -> String {
15+
match self::kind(&comment) {
16+
Some(Kind::SingleLines) => preprocess_single_lines(&comment),
17+
Some(Kind::MultiLine) => preprocess_multi_line(&comment),
18+
None => comment.to_owned(),
19+
}
20+
}
21+
22+
/// Gets the kind of the doc comment, if it is one.
23+
fn kind(comment: &str) -> Option<Kind> {
24+
if comment.starts_with("/*") {
25+
Some(Kind::MultiLine)
26+
} else if comment.starts_with("//") {
27+
Some(Kind::SingleLines)
28+
} else {
29+
None
30+
}
31+
}
32+
33+
/// Preprocesses mulitple single line comments.
34+
///
35+
/// Handles lines starting with both `//` and `///`.
36+
fn preprocess_single_lines(comment: &str) -> String {
37+
assert!(comment.starts_with("//"), "comment is not single line");
38+
39+
let lines: Vec<_> = comment.lines()
40+
.map(|l| l.trim_left_matches('/').trim())
41+
.map(|l| format!("/// {}", l).trim().to_owned())
42+
.collect();
43+
lines.join("\n")
44+
}
45+
46+
fn preprocess_multi_line(comment: &str) -> String {
47+
let comment = comment.trim_left_matches('/')
48+
.trim_left_matches("*")
49+
.trim_left_matches("!")
50+
.trim_right_matches('/')
51+
.trim_right_matches('*')
52+
.trim();
53+
54+
// Strip any potential `*` characters preceding each line.
55+
let mut lines: Vec<_> = comment.lines()
56+
.map(|line| line.trim().trim_left_matches('*').trim())
57+
.skip_while(|line| line.is_empty()) // Skip the first empty lines.
58+
.map(|line| format!("/// {}", line).trim().to_owned())
59+
.collect();
60+
61+
// Remove the trailing `*/`.
62+
let last_idx = lines.len() - 1;
63+
if lines[last_idx].is_empty() {
64+
lines.remove(last_idx);
65+
}
66+
67+
lines.join("\n")
68+
}
69+
70+
#[cfg(test)]
71+
mod test {
72+
use super::*;
73+
74+
#[test]
75+
fn picks_up_single_and_multi_line_doc_comments() {
76+
assert_eq!(kind("/// hello"), Some(Kind::SingleLines));
77+
assert_eq!(kind("/** world */"), Some(Kind::MultiLine));
78+
}
79+
80+
#[test]
81+
fn processes_single_lines_correctly() {
82+
assert_eq!(preprocess("/// hello".to_owned()), "/// hello");
83+
assert_eq!(preprocess("// hello".to_owned()), "/// hello");
84+
}
85+
86+
#[test]
87+
fn processes_multi_lines_correctly() {
88+
assert_eq!(preprocess("/** hello \n * world \n * foo \n */".to_owned()),
89+
"/// hello\n/// world\n/// foo");
90+
91+
assert_eq!(preprocess("/**\nhello\n*world\n*foo\n*/".to_owned()),
92+
"/// hello\n/// world\n/// foo");
93+
}
94+
}

src/ir/comp.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Compound types (unions and structs) in our intermediate representation.
22
33
use super::annotations::Annotations;
4+
use super::comment;
45
use super::context::{BindgenContext, ItemId};
56
use super::derive::{CanDeriveCopy, CanDeriveDebug, CanDeriveDefault};
67
use super::dot::DotAttributes;
@@ -1101,7 +1102,7 @@ impl CompInfo {
11011102
Some(potential_id),
11021103
ctx);
11031104

1104-
let comment = cur.raw_comment();
1105+
let comment = cur.raw_comment().map(comment::preprocess);
11051106
let annotations = Annotations::new(&cur);
11061107
let name = cur.spelling();
11071108
let is_mutable = cursor.is_mutable_field();

src/ir/enum_ty.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Intermediate representation for C/C++ enumerations.
22
3+
use super::comment;
34
use super::context::{BindgenContext, ItemId};
45
use super::item::Item;
56
use super::ty::TypeKind;
@@ -113,7 +114,7 @@ impl Enum {
113114
})
114115
});
115116

116-
let comment = cursor.raw_comment();
117+
let comment = cursor.raw_comment().map(comment::preprocess);
117118
variants.push(EnumVariant::new(name,
118119
comment,
119120
val,

src/ir/function.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Intermediate representation for C/C++ functions and methods.
22
3+
use super::comment;
34
use super::context::{BindgenContext, ItemId};
45
use super::dot::DotAttributes;
56
use super::item::Item;
@@ -405,7 +406,7 @@ impl ClangSubItemParser for Function {
405406
mangled_name = None;
406407
}
407408

408-
let comment = cursor.raw_comment();
409+
let comment = cursor.raw_comment().map(comment::preprocess);
409410

410411
let function = Self::new(name, mangled_name, sig, comment);
411412
Ok(ParseResult::New(function, Some(cursor)))

src/ir/item.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use super::super::codegen::CONSTIFIED_ENUM_MODULE_REPR_NAME;
44
use super::annotations::Annotations;
5+
use super::comment;
56
use super::context::{BindgenContext, ItemId, PartialType};
67
use super::derive::{CanDeriveCopy, CanDeriveDebug, CanDeriveDefault};
78
use super::dot::DotAttributes;
@@ -1001,7 +1002,7 @@ impl ClangItemParser for Item {
10011002
return Err(ParseError::Continue);
10021003
}
10031004

1004-
let comment = cursor.raw_comment();
1005+
let comment = cursor.raw_comment().map(comment::preprocess);
10051006
let annotations = Annotations::new(&cursor);
10061007

10071008
let current_module = ctx.current_module();
@@ -1207,7 +1208,8 @@ impl ClangItemParser for Item {
12071208
};
12081209

12091210
let comment = decl.raw_comment()
1210-
.or_else(|| location.raw_comment());
1211+
.or_else(|| location.raw_comment())
1212+
.map(comment::preprocess);
12111213
let annotations = Annotations::new(&decl)
12121214
.or_else(|| Annotations::new(&location));
12131215

src/ir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
pub mod annotations;
77
pub mod comp;
8+
pub mod comment;
89
pub mod context;
910
pub mod derive;
1011
pub mod dot;

tests/expectations/tests/accessors.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
#[derive(Debug, Default, Copy)]
99
pub struct SomeAccessors {
1010
pub mNoAccessor: ::std::os::raw::c_int,
11-
/** <div rustbindgen accessor></div> */
11+
/// <div rustbindgen accessor></div>
1212
pub mBothAccessors: ::std::os::raw::c_int,
13-
/** <div rustbindgen accessor="unsafe"></div> */
13+
/// <div rustbindgen accessor="unsafe"></div>
1414
pub mUnsafeAccessors: ::std::os::raw::c_int,
15-
/** <div rustbindgen accessor="immutable"></div> */
15+
/// <div rustbindgen accessor="immutable"></div>
1616
pub mImmutableAccessor: ::std::os::raw::c_int,
1717
}
1818
#[test]
@@ -68,7 +68,7 @@ impl SomeAccessors {
6868
&self.mImmutableAccessor
6969
}
7070
}
71-
/** <div rustbindgen accessor></div> */
71+
/// <div rustbindgen accessor></div>
7272
#[repr(C)]
7373
#[derive(Debug, Default, Copy)]
7474
pub struct AllAccessors {
@@ -114,7 +114,7 @@ impl AllAccessors {
114114
&mut self.mAlsoBothAccessors
115115
}
116116
}
117-
/** <div rustbindgen accessor="unsafe"></div> */
117+
/// <div rustbindgen accessor="unsafe"></div>
118118
#[repr(C)]
119119
#[derive(Debug, Default, Copy)]
120120
pub struct AllUnsafeAccessors {
@@ -162,16 +162,16 @@ impl AllUnsafeAccessors {
162162
&mut self.mAlsoBothAccessors
163163
}
164164
}
165-
/** <div rustbindgen accessor></div> */
165+
/// <div rustbindgen accessor></div>
166166
#[repr(C)]
167167
#[derive(Debug, Default, Copy)]
168168
pub struct ContradictAccessors {
169169
pub mBothAccessors: ::std::os::raw::c_int,
170-
/** <div rustbindgen accessor="false"></div> */
170+
/// <div rustbindgen accessor="false"></div>
171171
pub mNoAccessors: ::std::os::raw::c_int,
172-
/** <div rustbindgen accessor="unsafe"></div> */
172+
/// <div rustbindgen accessor="unsafe"></div>
173173
pub mUnsafeAccessors: ::std::os::raw::c_int,
174-
/** <div rustbindgen accessor="immutable"></div> */
174+
/// <div rustbindgen accessor="immutable"></div>
175175
pub mImmutableAccessor: ::std::os::raw::c_int,
176176
}
177177
#[test]
@@ -229,7 +229,7 @@ impl ContradictAccessors {
229229
&self.mImmutableAccessor
230230
}
231231
}
232-
/** <div rustbindgen accessor replaces="Replaced"></div> */
232+
/// <div rustbindgen accessor replaces="Replaced"></div>
233233
#[repr(C)]
234234
#[derive(Debug, Default, Copy)]
235235
pub struct Replaced {
@@ -258,7 +258,7 @@ impl Replaced {
258258
&mut self.mAccessor
259259
}
260260
}
261-
/** <div rustbindgen accessor></div> */
261+
/// <div rustbindgen accessor></div>
262262
#[repr(C)]
263263
#[derive(Debug, Default, Copy)]
264264
pub struct Wrapper {

tests/expectations/tests/annotation_hide.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
55

66

7-
/**
8-
* <div rustbindgen opaque></div>
9-
*/
7+
/// <div rustbindgen opaque></div>
108
#[repr(C)]
119
#[derive(Debug, Default, Copy)]
1210
pub struct D {

tests/expectations/tests/class_use_as.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
55

66

7-
/**
8-
* <div rustbindgen="true" replaces="whatever"></div>
9-
*/
7+
/// <div rustbindgen="true" replaces="whatever"></div>
108
#[repr(C)]
119
#[derive(Debug, Default, Copy)]
1210
pub struct whatever {

0 commit comments

Comments
 (0)