Pare down the Synom trait
I would like to make it clearer that parsing a string is second-class
functionality compared to parsing tokens.
diff --git a/src/file.rs b/src/file.rs
new file mode 100644
index 0000000..0cc1428
--- /dev/null
+++ b/src/file.rs
@@ -0,0 +1,46 @@
+use super::*;
+
+ast_struct! {
+ pub struct File {
+ pub shebang: Option<String>,
+ pub attrs: Vec<Attribute>,
+ pub items: Vec<Item>,
+ }
+}
+
+#[cfg(feature = "parsing")]
+pub mod parsing {
+ use super::*;
+
+ use synom::Synom;
+
+ impl Synom for File {
+ named!(parse -> Self, do_parse!(
+ attrs: many0!(call!(Attribute::parse_inner)) >>
+ items: many0!(syn!(Item)) >>
+ (File {
+ shebang: None,
+ attrs: attrs,
+ items: items,
+ })
+ ));
+
+ fn description() -> Option<&'static str> {
+ Some("crate")
+ }
+ }
+}
+
+#[cfg(feature = "printing")]
+mod printing {
+ use super::*;
+ use attr::FilterAttrs;
+ use quote::{Tokens, ToTokens};
+
+ impl ToTokens for File {
+ fn to_tokens(&self, tokens: &mut Tokens) {
+ tokens.append_all(self.attrs.inner());
+ tokens.append_all(&self.items);
+ }
+ }
+}
diff --git a/src/fold.rs b/src/fold.rs
index b39557a..99b66cb 100644
--- a/src/fold.rs
+++ b/src/fold.rs
@@ -13,7 +13,7 @@
/// method's default implementation recursively visits the substructure of the
/// input via the `noop_fold` methods, which perform an "identity fold", that
/// is, they return the same structure that they are given (for example the
-/// `fold_crate` method by default calls `fold::noop_fold_crate`).
+/// `fold_file` method by default calls `fold::noop_fold_file`).
///
/// If you want to ensure that your code handles every variant explicitly, you
/// need to override each method and monitor future changes to `Folder` in case
@@ -90,8 +90,8 @@
}
#[cfg(feature = "full")]
- fn fold_crate(&mut self, _crate: Crate) -> Crate {
- noop_fold_crate(self, _crate)
+ fn fold_file(&mut self, file: File) -> File {
+ noop_fold_file(self, file)
}
#[cfg(feature = "full")]
fn fold_item(&mut self, item: Item) -> Item {
@@ -564,15 +564,14 @@
}
#[cfg(feature = "full")]
-pub fn noop_fold_crate<F: ?Sized + Folder>(folder: &mut F,
- krate: Crate)
- -> Crate {
- Crate {
- attrs: krate.attrs.lift(|a| folder.fold_attribute(a)),
- items: krate.items.lift(|i| folder.fold_item(i)),
- ..krate
+pub fn noop_fold_file<F: ?Sized + Folder>(folder: &mut F,
+ file: File)
+ -> File {
+ File {
+ attrs: file.attrs.lift(|a| folder.fold_attribute(a)),
+ items: file.items.lift(|i| folder.fold_item(i)),
+ ..file
}
-
}
#[cfg(feature = "full")]
diff --git a/src/krate.rs b/src/krate.rs
deleted file mode 100644
index 7239bc3..0000000
--- a/src/krate.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use super::*;
-
-ast_struct! {
- pub struct Crate {
- pub shebang: Option<String>,
- pub attrs: Vec<Attribute>,
- pub items: Vec<Item>,
- }
-}
-
-#[cfg(feature = "parsing")]
-pub mod parsing {
- use super::*;
-
- use synom::{Synom, ParseError};
-
- impl Synom for Crate {
- named!(parse -> Self, do_parse!(
- attrs: many0!(call!(Attribute::parse_inner)) >>
- items: many0!(syn!(Item)) >>
- (Crate {
- shebang: None,
- attrs: attrs,
- items: items,
- })
- ));
-
- fn description() -> Option<&'static str> {
- Some("crate")
- }
-
- fn parse_str_all(mut input: &str) -> Result<Self, ParseError> {
- // Strip the BOM if it is present
- const BOM: &'static str = "\u{feff}";
- if input.starts_with(BOM) {
- input = &input[BOM.len()..];
- }
-
- let mut shebang = None;
- if input.starts_with("#!") && !input.starts_with("#![") {
- if let Some(idx) = input.find('\n') {
- shebang = Some(input[..idx].to_string());
- input = &input[idx..];
- } else {
- shebang = Some(input.to_string());
- input = "";
- }
- }
-
- let mut krate: Crate = Self::parse_all(input.parse()?)?;
- krate.shebang = shebang;
- Ok(krate)
- }
- }
-}
-
-#[cfg(feature = "printing")]
-mod printing {
- use super::*;
- use attr::FilterAttrs;
- use quote::{Tokens, ToTokens};
-
- impl ToTokens for Crate {
- fn to_tokens(&self, tokens: &mut Tokens) {
- tokens.append_all(self.attrs.inner());
- tokens.append_all(&self.items);
- }
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index a19581d..92cb9b2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -2,6 +2,7 @@
#![cfg_attr(feature = "cargo-clippy", allow(large_enum_variant))]
+extern crate proc_macro;
extern crate proc_macro2;
extern crate unicode_xid;
@@ -62,9 +63,9 @@
ArgSelf, ArgCaptured};
#[cfg(feature = "full")]
-mod krate;
+mod file;
#[cfg(feature = "full")]
-pub use krate::Crate;
+pub use file::File;
mod lifetime;
pub use lifetime::Lifetime;
@@ -100,45 +101,177 @@
#[cfg(feature = "fold")]
pub mod fold;
+////////////////////////////////////////////////////////////////////////////////
+
#[cfg(feature = "parsing")]
-mod parsing {
- use std::str::FromStr;
+pub use synom::ParseError;
- use super::*;
- use synom::{Synom, ParseError};
- use proc_macro2::TokenStream;
+#[cfg(feature = "parsing")]
+use synom::{Synom, SynomBuffer};
- macro_rules! traits {
- ($($ty:ident,)*) => ($(
- impl From<TokenStream> for $ty {
- fn from(stream: TokenStream) -> $ty {
- $ty::parse_all_unwrap(stream)
- }
+/// Parse tokens of source code into the chosen syn data type.
+///
+/// This is preferred over parsing a string because tokens are able to preserve
+/// information about where in the user's code they were originally written (the
+/// "span" of the token), possibly allowing the compiler to produce better error
+/// messages.
+///
+/// # Examples
+///
+/// ```rust,ignore
+/// extern crate proc_macro;
+/// use proc_macro::TokenStream;
+///
+/// extern crate syn;
+///
+/// #[macro_use]
+/// extern crate quote;
+///
+/// use syn::DeriveInput;
+///
+/// #[proc_macro_derive(MyMacro)]
+/// pub fn my_macro(input: TokenStream) -> TokenStream {
+/// // Parse the tokens into a syntax tree
+/// let ast: DeriveInput = syn::parse(input).unwrap();
+///
+/// // Build the output, possibly using quasi-quotation
+/// let expanded = quote! {
+/// /* ... */
+/// };
+///
+/// // Parse back to a token stream and return it
+/// expanded.parse().unwrap()
+/// }
+/// ```
+#[cfg(feature = "parsing")]
+pub fn parse<T>(tokens: proc_macro::TokenStream) -> Result<T, ParseError>
+ where T: Synom,
+{
+ _parse(tokens.into())
+}
+
+#[cfg(feature = "parsing")]
+fn _parse<T>(tokens: proc_macro2::TokenStream) -> Result<T, ParseError>
+ where T: Synom,
+{
+ let buf = SynomBuffer::new(tokens);
+ let result = T::parse(buf.begin());
+ let err = match result {
+ Ok((rest, t)) => {
+ if rest.eof() {
+ return Ok(t);
+ } else if rest == buf.begin() {
+ // parsed nothing
+ ParseError::new("failed to parse anything")
+ } else {
+ ParseError::new("failed to parse all tokens")
}
+ }
+ Err(err) => err,
+ };
+ match T::description() {
+ Some(s) => Err(ParseError::new(format!("parsing {}: {}", s, err))),
+ None => Err(err),
+ }
+}
- impl FromStr for $ty {
- type Err = ParseError;
+/// Parse a string of Rust code into the chosen syn data type.
+///
+/// # Examples
+///
+/// ```rust
+/// extern crate syn;
+/// #
+/// # #[macro_use]
+/// # extern crate error_chain;
+///
+/// use syn::Expr;
+/// #
+/// # error_chain! {
+/// # foreign_links {
+/// # Syn(syn::ParseError);
+/// # }
+/// # }
+///
+/// fn run() -> Result<()> {
+/// let code = "assert_eq!(u8::max_value(), 255)";
+/// let expr = syn::parse_str::<Expr>(code)?;
+/// println!("{:#?}", expr);
+/// Ok(())
+/// }
+/// #
+/// # fn main() { run().unwrap() }
+/// ```
+#[cfg(feature = "parsing")]
+pub fn parse_str<T: Synom>(s: &str) -> Result<T, ParseError> {
+ _parse(s.parse()?)
+}
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- $ty::parse_str_all(s)
- }
- }
- )*)
+// FIXME the name parse_file makes it sound like you might pass in a path to a
+// file, rather than the content.
+/// Parse the content of a file of Rust code.
+///
+/// This is different from `syn::parse_str::<File>(content)` in two ways:
+///
+/// - It discards a leading byte order mark `\u{FEFF}` if the file has one.
+/// - It preserves the shebang line of the file, such as `#!/usr/bin/env rustx`.
+///
+/// If present, either of these would be an error using `from_str`.
+///
+/// # Examples
+///
+/// ```rust,no_run
+/// extern crate syn;
+/// #
+/// # #[macro_use]
+/// # extern crate error_chain;
+///
+/// use std::fs::File;
+/// use std::io::Read;
+/// #
+/// # error_chain! {
+/// # foreign_links {
+/// # Io(std::io::Error);
+/// # Syn(syn::ParseError);
+/// # }
+/// # }
+///
+/// fn run() -> Result<()> {
+/// let mut file = File::open("path/to/code.rs")?;
+/// let mut content = String::new();
+/// file.read_to_string(&mut content)?;
+///
+/// let ast = syn::parse_file(&content)?;
+/// if let Some(shebang) = ast.shebang {
+/// println!("{}", shebang);
+/// }
+/// println!("{} items", ast.items.len());
+///
+/// Ok(())
+/// }
+/// #
+/// # fn main() { run().unwrap() }
+/// ```
+#[cfg(all(feature = "parsing", feature = "full"))]
+pub fn parse_file(mut content: &str) -> Result<File, ParseError> {
+ // Strip the BOM if it is present
+ const BOM: &'static str = "\u{feff}";
+ if content.starts_with(BOM) {
+ content = &content[BOM.len()..];
}
- traits! {
- DeriveInput,
- TyParamBound,
- Ident,
- WhereClause,
- Ty,
- Lit,
+ let mut shebang = None;
+ if content.starts_with("#!") && !content.starts_with("#![") {
+ if let Some(idx) = content.find('\n') {
+ shebang = Some(content[..idx].to_string());
+ content = &content[idx..];
+ } else {
+ shebang = Some(content.to_string());
+ content = "";
+ }
}
- #[cfg(feature = "full")]
- traits! {
- Expr,
- Item,
- Crate,
- }
+ let mut file: File = parse_str(content)?;
+ file.shebang = shebang;
+ Ok(file)
}
diff --git a/src/visit.rs b/src/visit.rs
index 5542c45..550afb7 100644
--- a/src/visit.rs
+++ b/src/visit.rs
@@ -82,8 +82,8 @@
}
#[cfg(feature = "full")]
- fn visit_crate(&mut self, _crate: &Crate) {
- walk_crate(self, _crate);
+ fn visit_file(&mut self, file: &File) {
+ walk_file(self, file);
}
#[cfg(feature = "full")]
fn visit_item(&mut self, item: &Item) {
@@ -330,9 +330,9 @@
}
#[cfg(feature = "full")]
-pub fn walk_crate<V: Visitor>(visitor: &mut V, _crate: &Crate) {
- walk_list!(visitor, visit_attribute, &_crate.attrs);
- walk_list!(visitor, visit_item, &_crate.items);
+pub fn walk_file<V: Visitor>(visitor: &mut V, file: &File) {
+ walk_list!(visitor, visit_attribute, &file.attrs);
+ walk_list!(visitor, visit_item, &file.items);
}
#[cfg(feature = "full")]