From dc0cf21403fd2f1fccfc8d5194d71a0700a37d42 Mon Sep 17 00:00:00 2001
From: Jorge Aparicio <jorge@japaric.io>
Date: Fri, 14 Jul 2017 20:46:10 -0500
Subject: [PATCH] split parsing from syntax checking

---
 src/check.rs | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++
 src/lib.rs   |  19 +++----
 src/parse.rs |  20 ++++---
 3 files changed, 169 insertions(+), 20 deletions(-)
 create mode 100644 src/check.rs

diff --git a/src/check.rs b/src/check.rs
new file mode 100644
index 0000000..6988783
--- /dev/null
+++ b/src/check.rs
@@ -0,0 +1,150 @@
+use std::collections::HashMap;
+
+use quote::Tokens;
+use syn::Ident;
+
+use error::*;
+use {Idents, Statics};
+
+pub type Tasks = HashMap<Ident, Task>;
+
+pub struct App {
+    pub device: Tokens,
+    pub idle: Idle,
+    pub init: Init,
+    pub resources: Statics,
+    pub tasks: Tasks,
+}
+
+pub struct Idle {
+    pub locals: Statics,
+    pub path: Tokens,
+    pub resources: Idents,
+}
+
+pub struct Init {
+    pub path: Tokens,
+}
+
+pub struct Task {
+    pub enabled: Option<bool>,
+    pub priority: Option<u8>,
+    pub resources: Idents,
+}
+
+pub fn app(app: ::App) -> Result<App> {
+    Ok(App {
+        device: app.device,
+        idle: ::check::idle(app.idle).chain_err(|| "checking `idle`")?,
+        init: ::check::init(app.init).chain_err(|| "checking `init`")?,
+        resources: ::check::statics("resources", app.resources)
+            .chain_err(|| "checking `resources`")?,
+        tasks: ::check::tasks(app.tasks).chain_err(|| "checking `tasks`")?,
+    })
+}
+
+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!(
+            idle.locals.is_some() || idle.path.is_some() ||
+                idle.resources.is_some(),
+            "empty `idle` field. It should be removed."
+        );
+
+        Idle {
+            locals: ::check::statics("locals", idle.locals)?,
+            path: ::check::path("idle", idle.path)
+                .chain_err(|| "checking `path`")?,
+            resources: ::check::idents("resources", idle.resources)?,
+        }
+    } else {
+        Idle {
+            locals: Statics::new(),
+            path: quote!(idle),
+            resources: Idents::new(),
+        }
+    })
+}
+
+fn init(init: Option<::Init>) -> Result<Init> {
+    Ok(if let Some(init) = init {
+        if let Some(path) = init.path {
+            Init {
+                path: ::check::path("init", Some(path))
+                    .chain_err(|| "checking `path`")?,
+            }
+        } else {
+            bail!("empty `init` field. It should be removed.");
+        }
+    } else {
+        Init { path: quote!(init) }
+    })
+}
+
+fn path(default: &str, path: Option<Tokens>) -> Result<Tokens> {
+    Ok(if let Some(path) = path {
+        ensure!(
+            path.as_str() != default,
+            "this is the default value. It should be omitted."
+        );
+
+        path
+    } else {
+        let default = Ident::new(default);
+        quote!(#default)
+    })
+}
+
+fn statics(field: &str, statics: Option<Statics>) -> Result<Statics> {
+    Ok(if let Some(statics) = statics {
+        ensure!(
+            !statics.is_empty(),
+            "empty `{}` field. It should be removed.",
+            field
+        );
+
+        statics
+    } else {
+        Statics::new()
+    })
+}
+
+fn tasks(tasks: Option<::Tasks>) -> Result<Tasks> {
+    Ok(if let Some(tasks) = tasks {
+        ensure!(
+            !tasks.is_empty(),
+            "empty `tasks` field. It should be removed"
+        );
+
+        tasks
+            .into_iter()
+            .map(|(name, task)| {
+                Ok((
+                    name.clone(),
+                    Task {
+                        enabled: task.enabled,
+                        priority: task.priority,
+                        resources: ::check::idents("resources", task.resources)
+                            .chain_err(|| format!("checking task `{}`", name))?,
+                    },
+                ))
+            })
+            .collect::<Result<_>>()?
+    } else {
+        Tasks::new()
+    })
+}
diff --git a/src/lib.rs b/src/lib.rs
index 015a3a9..f010d68 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@ extern crate error_chain;
 extern crate quote;
 extern crate syn;
 
+pub mod check;
 pub mod error;
 
 mod parse;
@@ -26,31 +27,31 @@ pub type Tasks = HashMap<Ident, Task>;
 #[derive(Debug)]
 pub struct App {
     pub device: Tokens,
-    pub idle: Idle,
-    pub init: Init,
-    pub resources: Statics,
-    pub tasks: Tasks,
+    pub idle: Option<Idle>,
+    pub init: Option<Init>,
+    pub resources: Option<Statics>,
+    pub tasks: Option<Tasks>,
 }
 
 /// `init`
 #[derive(Debug)]
 pub struct Init {
-    pub path: Tokens,
+    pub path: Option<Tokens>,
 }
 
 /// `idle`
 #[derive(Debug)]
 pub struct Idle {
-    pub locals: Statics,
-    pub path: Tokens,
-    pub resources: Idents,
+    pub locals: Option<Statics>,
+    pub path: Option<Tokens>,
+    pub resources: Option<Idents>,
 }
 
 #[derive(Debug)]
 pub struct Task {
     pub enabled: Option<bool>,
     pub priority: Option<u8>,
-    pub resources: Idents,
+    pub resources: Option<Idents>,
 }
 
 // `$ident: $ty = $expr;`
diff --git a/src/parse.rs b/src/parse.rs
index 98a94be..b77b7d3 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -57,10 +57,10 @@ pub fn app(input: &str) -> Result<App> {
 
     Ok(App {
         device: device.ok_or("`device` field is missing")?,
-        idle: idle.ok_or("`idle` field is missing")?,
-        init: init.ok_or("`init` field is missing")?,
-        resources: resources.unwrap_or(HashMap::new()),
-        tasks: tasks.unwrap_or(HashMap::new()),
+        idle,
+        init,
+        resources,
+        tasks,
     })
 }
 
@@ -199,9 +199,9 @@ fn idle(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Idle> {
         })?;
 
         Ok(Idle {
-            locals: locals.unwrap_or(HashMap::new()),
-            path: path.ok_or("`path` field missing")?,
-            resources: resources.unwrap_or(HashSet::new()),
+            locals,
+            path,
+            resources,
         })
     })
 }
@@ -223,9 +223,7 @@ fn init(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Init> {
             Ok(())
         })?;
 
-        Ok(Init {
-            path: path.ok_or("`path` field missing")?,
-        })
+        Ok(Init { path })
     })
 }
 
@@ -360,7 +358,7 @@ fn task(tts: &mut Peekable<Iter<TokenTree>>) -> Result<Task> {
         Ok(Task {
             enabled,
             priority,
-            resources: resources.unwrap_or(HashSet::new()),
+            resources,
         })
     })
 }
-- 
GitLab