From 596644891f78a2671ab15a120fdd7cbab5c09fd1 Mon Sep 17 00:00:00 2001
From: Jorge Aparicio <jorge@japaric.io>
Date: Fri, 28 Jul 2017 22:55:17 -0500
Subject: [PATCH] v0.1.0

---
 CHANGELOG.md |  12 ++++++
 Cargo.toml   |   6 +++
 README.md    |  41 ++++++++++---------
 src/check.rs |  74 ++++++++++++++++++++++++---------
 src/error.rs |   2 +
 src/lib.rs   |  50 +++++++++++++++++++----
 src/parse.rs | 113 +++++++++++++++++++++++++++++++--------------------
 7 files changed, 205 insertions(+), 93 deletions(-)
 create mode 100644 CHANGELOG.md

diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..bc67dd4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+This project adheres to [Semantic Versioning](http://semver.org/).
+
+## [Unreleased]
+
+## v0.1.0 - 2017-07-28
+
+- Initial release
+
+[Unreleased]: https://github.com/japaric/rtfm-syntax/compare/v0.1.0...HEAD
diff --git a/Cargo.toml b/Cargo.toml
index 81a971d..6ffcd3a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,12 @@
 [package]
 authors = ["Jorge Aparicio <jorge@japaric.io>"]
+categories = ["concurrency", "embedded", "no-std"]
+description = "Parser of the app! macro used by the Real Time for The Masses (RTFM) framework"
+documentation = "https://docs.rs/rtfm-syntax"
+keywords = []
+license = "MIT OR Apache-2.0"
 name = "rtfm-syntax"
+repository = "https://github.com/japaric/rtfm-syntax"
 version = "0.1.0"
 
 [dependencies]
diff --git a/README.md b/README.md
index f746e06..25e09c5 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,22 @@
-# `rtfm-syntax`
-
-> `rtfm!` macro parser
-
-## License
-
-Licensed under either of
-
-- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
-  http://www.apache.org/licenses/LICENSE-2.0)
-- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
-
-at your option.
-
-### Contribution
-
-Unless you explicitly state otherwise, any contribution intentionally submitted
-for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
-dual licensed as above, without any additional terms or conditions.
+# `rtfm-syntax`
+
+> Parser of the `app!` macro used by the Real Time For the Masses (RTFM)
+> framework
+
+## [Documentation](https://docs.rs/rtfm-syntax)
+
+## License
+
+Licensed under either of
+
+- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
+  http://www.apache.org/licenses/LICENSE-2.0)
+- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
+dual licensed as above, without any additional terms or conditions.
diff --git a/src/check.rs b/src/check.rs
index 3ba3e97..f143c90 100644
--- a/src/check.rs
+++ b/src/check.rs
@@ -1,38 +1,67 @@
+//! Syntax checking pass
+
 use std::collections::HashMap;
 
 use syn::{Ident, Path};
 
 use error::*;
-use {util, Idents, Statics};
+use {util, Resources, Statics};
 
+/// `$($Ident: { .. },)*`
 pub type Tasks = HashMap<Ident, Task>;
 
+/// `app! { .. }`
+#[derive(Debug)]
 pub struct App {
+    /// `device: $path`
     pub device: Path,
+    /// `idle: { $Idle }`
     pub idle: Idle,
+    /// `init: { $Init }`
     pub init: Init,
+    /// `resources: $Statics`
     pub resources: Statics,
+    /// `tasks: { $Tasks }`
     pub tasks: Tasks,
+    _extensible: (),
 }
 
+/// `idle: { .. }`
+#[derive(Debug)]
 pub struct Idle {
+    /// `path: $Path`
     pub path: Path,
-    pub resources: Idents,
+    /// `resources: $Resources`
+    pub resources: Resources,
+    _extensible: (),
 }
 
+/// `init: { .. }`
+#[derive(Debug)]
 pub struct Init {
+    /// `path: $Path`
     pub path: Path,
+    _extensible: (),
 }
 
+/// `$Ident: { .. }`
+#[derive(Debug)]
 pub struct Task {
+    /// `enabled: $bool`
     pub enabled: Option<bool>,
+    /// `path: $Path`
     pub path: Option<Path>,
+    /// `priority: $u8`
     pub priority: Option<u8>,
-    pub resources: Idents,
+    /// `resources: $Resources`
+    pub resources: Resources,
+    _extensible: (),
 }
 
+/// Checks the syntax of the parsed `app!` macro
 pub fn app(app: ::App) -> Result<App> {
     Ok(App {
+        _extensible: (),
         device: app.device,
         idle: ::check::idle(app.idle).chain_err(|| "checking `idle`")?,
         init: ::check::init(app.init).chain_err(|| "checking `init`")?,
@@ -42,20 +71,6 @@ pub fn app(app: ::App) -> Result<App> {
     })
 }
 
-fn idents(field: &str, idents: Option<Idents>) -> Result<Idents> {
-    Ok(if let Some(idents) = idents {
-        ensure!(
-            !idents.is_empty(),
-            "empty `{}` field. It should be removed.",
-            field
-        );
-
-        idents
-    } else {
-        Idents::new()
-    })
-}
-
 fn idle(idle: Option<::Idle>) -> Result<Idle> {
     Ok(if let Some(idle) = idle {
         ensure!(
@@ -64,14 +79,16 @@ fn idle(idle: Option<::Idle>) -> Result<Idle> {
         );
 
         Idle {
+            _extensible: (),
             path: ::check::path("idle", idle.path)
                 .chain_err(|| "checking `path`")?,
-            resources: ::check::idents("resources", idle.resources)?,
+            resources: ::check::resources("resources", idle.resources)?,
         }
     } else {
         Idle {
+            _extensible: (),
             path: util::mk_path("idle"),
-            resources: Idents::new(),
+            resources: Resources::new(),
         }
     })
 }
@@ -80,6 +97,7 @@ fn init(init: Option<::Init>) -> Result<Init> {
     Ok(if let Some(init) = init {
         if let Some(path) = init.path {
             Init {
+                _extensible: (),
                 path: ::check::path("init", Some(path))
                     .chain_err(|| "checking `path`")?,
             }
@@ -88,6 +106,7 @@ fn init(init: Option<::Init>) -> Result<Init> {
         }
     } else {
         Init {
+            _extensible: (),
             path: util::mk_path("init"),
         }
     })
@@ -107,6 +126,20 @@ fn path(default: &str, path: Option<Path>) -> Result<Path> {
     })
 }
 
+fn resources(field: &str, idents: Option<Resources>) -> Result<Resources> {
+    Ok(if let Some(idents) = idents {
+        ensure!(
+            !idents.is_empty(),
+            "empty `{}` field. It should be removed.",
+            field
+        );
+
+        idents
+    } else {
+        Resources::new()
+    })
+}
+
 fn statics(field: &str, statics: Option<Statics>) -> Result<Statics> {
     Ok(if let Some(statics) = statics {
         ensure!(
@@ -136,10 +169,11 @@ fn tasks(tasks: Option<::Tasks>) -> Result<Tasks> {
                     Ok((
                         name,
                         Task {
+                            _extensible: (),
                             enabled: task.enabled,
                             path: task.path,
                             priority: task.priority,
-                            resources: ::check::idents(
+                            resources: ::check::resources(
                                 "resources",
                                 task.resources,
                             )?,
diff --git a/src/error.rs b/src/error.rs
index c044473..f10f6df 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1 +1,3 @@
+//! Errors
+
 error_chain!();
diff --git a/src/lib.rs b/src/lib.rs
index 7840e86..72efece 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,7 @@
+//! Parser of the `app!` macro used by the Real Time For the Masses (RTFM)
+//! framework
+#![deny(missing_debug_implementations)]
+#![deny(missing_docs)]
 #![deny(warnings)]
 
 #[macro_use]
@@ -19,50 +23,78 @@ use syn::{Ident, Path, Ty};
 
 use error::*;
 
-pub type Idents = HashSet<Ident>;
+/// A rust expression
+pub type Expr = Tokens;
 
+/// `[$($ident),*]`
+pub type Resources = HashSet<Ident>;
+
+/// `$(static $Ident: $Ty = $expr;)*`
 pub type Statics = HashMap<Ident, Static>;
 
+/// `$($Ident: { .. },)*`
 pub type Tasks = HashMap<Ident, Task>;
 
+/// `app! { .. }`
 #[derive(Debug)]
 pub struct App {
+    /// `device: $path`
     pub device: Path,
+    /// `idle: { $Idle }`
     pub idle: Option<Idle>,
+    /// `init: { $Init }`
     pub init: Option<Init>,
+    /// `resources: $Resources`
     pub resources: Option<Statics>,
+    /// `tasks: { $Tasks }`
     pub tasks: Option<Tasks>,
+    _extensible: (),
 }
 
-/// `init`
+/// `idle: { .. }`
 #[derive(Debug)]
-pub struct Init {
+pub struct Idle {
+    /// `path: $Path`
     pub path: Option<Path>,
+    /// `resources: $Resources`
+    pub resources: Option<Resources>,
+    _extensible: (),
 }
 
-/// `idle`
+/// `init: { .. }`
 #[derive(Debug)]
-pub struct Idle {
+pub struct Init {
+    /// `path: $Path`
     pub path: Option<Path>,
-    pub resources: Option<Idents>,
+    _extensible: (),
 }
 
+/// `$Ident: { .. }`
 #[derive(Debug)]
 pub struct Task {
+    /// `enabled: $bool`
     pub enabled: Option<bool>,
+    /// `path: $Path`
     pub path: Option<Path>,
+    /// `priority: $u8`
     pub priority: Option<u8>,
-    pub resources: Option<Idents>,
+    /// `resources: $Resources`
+    pub resources: Option<Resources>,
+    _extensible: (),
 }
 
-// `$ident: $ty = $expr;`
+/// `static $Ident: $Ty = $Expr;`
 #[derive(Debug)]
 pub struct Static {
-    pub expr: Tokens,
+    /// `$Expr`
+    pub expr: Expr,
+    /// `$Ty`
     pub ty: Ty,
+    _extensible: (),
 }
 
 impl App {
+    /// Parses the contents of the `app! { .. }` macro
     pub fn parse(input: &str) -> Result<Self> {
         parse::app(input)
     }
diff --git a/src/parse.rs b/src/parse.rs
index a00285a..ab832ad 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -6,8 +6,9 @@ use syn::{self, DelimToken, Ident, IntTy, Lit, Path, Token, TokenTree};
 
 use error::*;
 
-use {App, Idents, Idle, Init, Static, Statics, Task, Tasks};
+use {App, Idle, Init, Resources, Static, Statics, Task, Tasks};
 
+/// Parses the contents of `app! { $App }`
 pub fn app(input: &str) -> Result<App> {
     let tts = syn::parse_token_trees(input)?;
 
@@ -55,6 +56,7 @@ pub fn app(input: &str) -> Result<App> {
     })?;
 
     Ok(App {
+        _extensible: (),
         device: device.ok_or("`device` field is missing")?,
         idle,
         init,
@@ -63,6 +65,7 @@ pub fn app(input: &str) -> Result<App> {
     })
 }
 
+/// Parses a boolean
 fn bool(tt: Option<&TokenTree>) -> Result<bool> {
     if let Some(&TokenTree::Token(Token::Literal(Lit::Bool(bool)))) = tt {
         Ok(bool)
@@ -71,6 +74,7 @@ fn bool(tt: Option<&TokenTree>) -> Result<bool> {
     }
 }
 
+/// Parses a delimited token tree
 fn delimited<R, F>(
     tts: &mut Peekable<Iter<TokenTree>>,
     delimiter: DelimToken,
@@ -94,7 +98,7 @@ where
     }
 }
 
-// `$($key:ident: $($value:tt)*),*[,]`
+/// Parses `$($Ident: $($tt)*,)*`
 fn fields<F>(tts: &[TokenTree], mut f: F) -> Result<()>
 where
     F: FnMut(&Ident, &mut Peekable<Iter<TokenTree>>) -> Result<()>,
@@ -126,42 +130,7 @@ where
     Ok(())
 }
 
-fn idents(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Idents> {
-    ::parse::delimited(tts, DelimToken::Bracket, |tts| {
-        let mut idents = HashSet::new();
-
-        let mut tts = tts.iter().peekable();
-        while let Some(tt) = tts.next() {
-            if let &TokenTree::Token(Token::Ident(ref ident)) = tt {
-                ensure!(
-                    !idents.contains(ident),
-                    "ident {} listed more than once"
-                );
-
-                idents.insert(ident.clone());
-
-                if let Some(tt) = tts.next() {
-                    ensure!(
-                        tt == &TokenTree::Token(Token::Comma),
-                        "expected Comma, found {:?}",
-                        tt
-                    );
-
-                    if tts.peek().is_none() {
-                        break;
-                    }
-                } else {
-                    break;
-                }
-            } else {
-                bail!("expected Ident, found {:?}", tt);
-            }
-        }
-
-        Ok(idents)
-    })
-}
-
+/// Parses the LHS of `idle: { $Idle }`
 fn idle(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Idle> {
     ::parse::delimited(tts, DelimToken::Brace, |tts| {
         let mut path = None;
@@ -180,7 +149,7 @@ fn idle(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Idle> {
                         "duplicated `resources` field"
                     );
 
-                    resources = Some(::parse::idents(tts)
+                    resources = Some(::parse::resources(tts)
                         .chain_err(|| "parsing `resources`")?);
                 }
                 _ => bail!("unknown field: `{}`", key),
@@ -189,10 +158,15 @@ fn idle(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Idle> {
             Ok(())
         })?;
 
-        Ok(Idle { path, resources })
+        Ok(Idle {
+            _extensible: (),
+            path,
+            resources,
+        })
     })
 }
 
+/// Parses the LHS of `init: { $Init }`
 fn init(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Init> {
     ::parse::delimited(tts, DelimToken::Brace, |tts| {
         let mut path = None;
@@ -210,11 +184,51 @@ fn init(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Init> {
             Ok(())
         })?;
 
-        Ok(Init { path })
+        Ok(Init {
+            _extensible: (),
+            path,
+        })
+    })
+}
+
+/// Parses `[$($Ident,)*]`
+fn resources(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Resources> {
+    ::parse::delimited(tts, DelimToken::Bracket, |tts| {
+        let mut idents = HashSet::new();
+
+        let mut tts = tts.iter().peekable();
+        while let Some(tt) = tts.next() {
+            if let &TokenTree::Token(Token::Ident(ref ident)) = tt {
+                ensure!(
+                    !idents.contains(ident),
+                    "ident {} listed more than once"
+                );
+
+                idents.insert(ident.clone());
+
+                if let Some(tt) = tts.next() {
+                    ensure!(
+                        tt == &TokenTree::Token(Token::Comma),
+                        "expected Comma, found {:?}",
+                        tt
+                    );
+
+                    if tts.peek().is_none() {
+                        break;
+                    }
+                } else {
+                    break;
+                }
+            } else {
+                bail!("expected Ident, found {:?}", tt);
+            }
+        }
+
+        Ok(idents)
     })
 }
 
-/// `$ty:ty = $expr:expr`
+/// Parses `$Ty = $Expr`
 fn static_(tts: &mut Iter<TokenTree>) -> Result<Static> {
     let mut fragments = vec![];
     loop {
@@ -247,10 +261,14 @@ fn static_(tts: &mut Iter<TokenTree>) -> Result<Static> {
     ensure!(!fragments.is_empty(), "initial value is missing");
     let expr = quote!(#(#fragments)*);
 
-    Ok(Static { expr, ty })
+    Ok(Static {
+        _extensible: (),
+        expr,
+        ty,
+    })
 }
 
-/// $($ident:ident: $ty:ty = $expr:expr);*
+/// Parses `$($Ident: $Ty = $Expr;)*`
 fn statics(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Statics> {
     ::parse::delimited(tts, DelimToken::Brace, |tts| {
         let mut statics = HashMap::new();
@@ -296,6 +314,7 @@ fn statics(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Statics> {
     })
 }
 
+/// Parses a `Path` from `$($tt)*`
 fn path(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Path> {
     let mut fragments = vec![];
 
@@ -316,6 +335,7 @@ fn path(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Path> {
     Ok(syn::parse_path(&format!("{}", quote!(#(#fragments)*)))?)
 }
 
+/// Parses the LHS of `$Ident: { .. }`
 fn task(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Task> {
     ::parse::delimited(tts, DelimToken::Brace, |tts| {
         let mut enabled = None;
@@ -350,7 +370,7 @@ fn task(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Task> {
                         "duplicated `resources` field"
                     );
 
-                    resources = Some(::parse::idents(tts)
+                    resources = Some(::parse::resources(tts)
                         .chain_err(|| "parsing `resources`")?);
                 }
                 _ => bail!("unknown field: `{}`", key),
@@ -360,6 +380,7 @@ fn task(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Task> {
         })?;
 
         Ok(Task {
+            _extensible: (),
             enabled,
             path,
             priority,
@@ -368,6 +389,7 @@ fn task(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Task> {
     })
 }
 
+/// Parses `$($Ident: { $Task })*`
 fn tasks(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Tasks> {
     ::parse::delimited(tts, DelimToken::Brace, |tts| {
         let mut tasks = HashMap::new();
@@ -392,6 +414,7 @@ fn tasks(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Tasks> {
     })
 }
 
+/// Parses an integer with type `u8`
 fn u8(tt: Option<&TokenTree>) -> Result<u8> {
     if let Some(
         &TokenTree::Token(
-- 
GitLab