diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..bc67dd40c6c4772392bb43d88bd1bbb93cc8ad96 --- /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 81a971dfa3e1185b759e35f73b6199dd49e15508..6ffcd3a542ca215f5c0b80685db36f5f71d35cb6 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 f746e06b084072e33d1f1a94eecc006a15ab4ae1..25e09c51c051bcb388a6ef041738c7f0325c9c07 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 3ba3e97d29bb1959e9e12d15b71d336628910e18..f143c90b01c76f413866d78752e8278ac87a2f5d 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 c044473728cb0f1eddf88fade3ac6bd7897da683..f10f6dfeae50c21884f89f9b3a76720ab0aee593 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 7840e86d61e58e2eb279adc416c3c8d4fb726fcc..72efece8e9672f4207635e26e881141a52458f6e 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 a00285aae08d77a0cc31d556c9824ac0fd91bc50..ab832ad001c047024a15026899e09a141c52452f 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(