diff --git a/Cargo.toml b/Cargo.toml index 0344cfe..1f33891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ documentation = "https://docs.avdanos.com/" homepage = "https://avdanos.com/" license = "GPL-3.0" version = "0.0.1" -authors = ["Akane ", "Froxcey ", "Sammy "] +authors = ["Akane ", "Froxcey ", "Sammy "] edition = "2021" build = "build.rs" @@ -18,6 +18,15 @@ json_comments = "0.2.1" regex = "1.6.0" slog-stdlog = "4.1.1" bitflags = "1" +colored = "2.0.0" + +[dependencies.compositor-macros] +path = "./src/macros" + +[dependencies.json-tree] + +git = "https://github.com/Sammy99jsp/json-tree.git" +features = ["jsonc"] [dependencies.smithay] diff --git a/src/config/config.rs b/src/config/config.rs index 645954a..14dad37 100644 --- a/src/config/config.rs +++ b/src/config/config.rs @@ -10,7 +10,7 @@ use lazy_static::lazy_static; pub(crate) use json_comments::StripComments; -use json_tree::{Index, TokenContent}; +use json_tree::{Index,}; use serde::Deserialize; use crate::{CONST::{ @@ -18,31 +18,57 @@ use crate::{CONST::{ CONFIG_FILE, }, config::errors::UnexpectedToken}; -use super::sections::{keybinds::Keybinds, section::ConfigurationSection}; +use super::sections::{keybinds::Keybinds}; lazy_static! { - pub static ref PATH : String = CONFIG_FOLDER.join(*CONFIG_FILE).to_string_lossy().to_string(); + pub static ref PATH : String = CONFIG_FOLDER.join(*CONFIG_FILE).to_string_lossy().to_string(); } +static mut INDEX : Option = None; -static mut _INDEX : Option = None; - +static mut CONFIG : Option = None; #[derive(Deserialize, Debug)] pub struct Config { - keybinds: ::Raw, + pub keybinds: Keybinds } impl Config { + + pub fn path() -> String { + PATH.to_string() + } + + /// + /// Returns the config file's JSON index. + /// pub fn index<'a>() -> &'a Index { unsafe { - _INDEX.as_ref().unwrap() + INDEX.as_ref().unwrap() + } + } + + /// + /// Returns the Global Configuration Object. + /// + pub fn config<'a>() -> &'a Self { + unsafe { + CONFIG.as_ref().unwrap() } } - pub fn from_file() -> Result> { + + + /// + /// Loads the config. + /// + /// THIS FUNCTION SHOULD BE NEAR THE TOP OF `main.rs` + /// + pub fn load() -> Result<(), Box> { let path = PATH.to_string(); fs::create_dir_all(*CONFIG_FOLDER) .expect("Error while creating the AvdanOS config directory!"); + // TODO: If config file not found, either download config + // or use a pre-bundled copy. let file: File = fs::OpenOptions::new() .read(true).write(true).create(true) .open(&path)?; @@ -91,32 +117,17 @@ impl Config { index }; - let mut parsed: Config = serde_json::from_reader(stripped)?; - - unsafe { - _INDEX = Some(src_map); + INDEX = Some(src_map); } - let result = Keybinds::parse( - Keybinds::traceable( - Some(true) - ), - &parsed.keybinds, - Self::index() - ); - - match result { - Ok(k) => { - return Ok(parsed) - }, - Err(errs) => { - for err in errs { - println!("{}\n", err); - } + let o = serde_json::from_reader(stripped)?; + + + unsafe { + CONFIG = Some(o); + }; - panic!() - } - } + Ok(()) } } \ No newline at end of file diff --git a/src/config/errors.rs b/src/config/errors.rs index 2cd4a77..87a2320 100644 --- a/src/config/errors.rs +++ b/src/config/errors.rs @@ -3,7 +3,7 @@ use compositor_macros::AvError; use json_tree::{ParserError, TokenContent}; use crate::core::error::{TraceableError, AvError, Traceable}; -use super::config::{self, Config}; +use super::config; /// diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..29f644c --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,8 @@ +pub mod config; +pub mod sections; +pub mod section; +pub mod templating; +pub mod errors; + +pub use config::Config; +pub use section::ConfigurationSection; \ No newline at end of file diff --git a/src/config/section.rs b/src/config/section.rs new file mode 100644 index 0000000..2ebb2b1 --- /dev/null +++ b/src/config/section.rs @@ -0,0 +1,196 @@ +use std::collections::HashMap; + +use colored::Colorize; +use compositor_macros::{AvError, location, description}; +use json_tree::{JSONPath, }; + +use crate::{core::error::{TraceableError, Traceable, AvError}, config::{config::{self, Config}, templating::{r#macro::SignatureMismatchError, AvMacro, avvalue::AvValue}}, Nadva::error::compare_errors}; + +pub trait ConfigurationSection : Sized { + /// + /// The absolute path to this section as a str. + /// + const PATH : &'static str; + + /// + /// Returns the absolute path to this section. + /// Can be used in finding location of data. + /// + fn path() -> JSONPath { + Self::PATH.to_string().try_into().unwrap() + } + + /// + /// Returns this section's traceable. + /// + fn traceable(key : Option) -> Traceable { + let loc = Config::index().get(&Self::path()).unwrap(); + Traceable::combine(&config::PATH.to_string(), loc, key) + } + + fn from_map(declared : HashMap, raw : HashMap) -> HashMap { + let path : JSONPath = Self::path(); + + let res = raw.iter().map(|(k, v)| { + // Parse as a macro. + let p = path.push(k.clone()); + let loc = Config::index().get(&p).unwrap(); + let k_pos = Traceable::combine(&Config::path(), loc, Some(true)); + let v_pos = Traceable::combine(&Config::path(), loc, Some(false)); + + ( + AvMacro::parse( + k_pos.clone(), + k.clone() + ), + v, + k_pos, + v_pos + ) + }); + + // Syntactically invalid macros. + + let mut errors : Vec<(Box, Traceable)> = vec![]; + + res + .clone() + .filter(|(k, _, _, _)| k.is_err()) + .for_each(|(k, _, p, _)| { + let n = k.unwrap_err(); + + for err in n { + errors.push((err, p.clone())); + } + } + ); + + + // Syntactically Valid macros + let defined = res + .filter(|(k, _, _, _)| k.is_ok()) + .map(|(k, v, p1, p2)| (k.unwrap(), v, p1, p2)); + + + let mut output : HashMap = HashMap::new(); + + + let mut found_macros : Vec = vec![]; + // Look up the valid macros against our declared HashMap. + + for (declared_m, default_v) in declared { + let defined_m = defined.clone().position(|(m, _, _, _)| m.identifier() == declared_m.identifier()); + + let (avmacro, avvalue) = match defined_m { + None => { + // Macro not in user's config, + // use default + // (and possibly issue a warning). + + // TODO: @Sammy99jsp add 'not found' warning. + + + errors.push(( + Box::new( + MacroMissing(Self::traceable(Some(false)), declared_m.identifier(), Self::path()) + ), + Self::traceable(Some(false)) + )); + (declared_m, default_v) + }, + Some(i) => { + found_macros.push(i); + let (m, v, p, p_v) = defined.clone().nth(i).unwrap(); + + // Check if the macro's signature matches our defined one. + let sig_check = declared_m.has_signature(&m); + + if let Err((delta, vec)) = sig_check { + errors.push(( + Box::new( + SignatureMismatchError( + p.clone(), + m.identifier(), + (delta, vec.iter().map(|e| (*e).clone()).collect()) + ) + ) as Box, + p + )); + + (declared_m, default_v) + } else { + // VALUE CHECKS + // Now check the value's type against the default's + match default_v + .parse_same_type(p_v.clone(), v.clone()) + { + Err(e) => { + errors.push((e, p)); + (declared_m, default_v) + }, + Ok(val) => { + // Last value check: + // Check if value is consistent with macro + match val.consistent_with_macro(p_v.clone(), &m) { + Ok(()) => (declared_m, val), + Err(e) => { + errors.push((e, p)); + (declared_m, default_v) + }, + } + } + } + } + + } + }; + + output.insert(avmacro, avvalue); + } + + // User defined macros which were not found in our declaration. + let not_found : Vec<_> = defined + .enumerate() + .filter(|(i, _)| !found_macros.contains(i)) + .map(|(_, e)| e) + .collect(); + + for (m, _, p1, _) in not_found { + errors.push( + ( + Box::new( + MacroNotFound(p1.clone(), m.identifier(), Self::path()) + ) as Box, + p1 + ) + ) + } + + errors + .sort_by(|(a, _), (b, _)| + compare_errors(a, b).unwrap() + ); + + for (err, _) in errors { + println!("{}", err); + } + + output + } +} + +#[AvError(TraceableError, CONFIG_MACRO_NOT_FOUND, "Config: Macro Not Found")] +pub struct MacroNotFound(pub Traceable, pub String, pub JSONPath); + +impl TraceableError for MacroNotFound { + location!(&self.0); + description!(("The macro `{}` is not defined in this section (`{}`) -- we've used the default.", self.1.blue(), self.2)); +} + +#[AvError(TraceableError, CONFIG_MACRO_MISSING, "Config: Macro Missing")] +pub struct MacroMissing(pub Traceable, pub String, pub JSONPath); + +impl TraceableError for MacroMissing { + location!(&self.0); + description!(("The macro `{}` wasn't found in {}.", self.1.blue(), self.2)); +} \ No newline at end of file diff --git a/src/config/sections/keybinds.rs b/src/config/sections/keybinds.rs index b61a054..d085b59 100644 --- a/src/config/sections/keybinds.rs +++ b/src/config/sections/keybinds.rs @@ -1,116 +1,17 @@ -use std::collections::HashMap; +use compositor_macros::config_section; -use compositor_macros::export_test; -use json_tree::{Index, Location}; -use serde::Deserialize; +use crate::config::ConfigurationSection; -use lazy_static::lazy_static; - - -use crate::{core::error::{TraceableError, Traceable}, config::templating::AvMacro}; - -use super::section::ConfigurationSection; - -export_test!( +config_section!( Keybinds { - "Move focused window to `R`th on the taskbar." - window(d) => (Super+{d}), - - "Toggle the active window into fullscreen mode." - full_screen() => (Super+F), - - "Switch the focused workspace to workspace `{d}`." - workspace(d) => (Super+{d}), - - "Move the focused window to workspace `{d}`." - move_window_to_workspace(d) => (Super+Shift+{d}), - - "Close the current window." - close_window() => (Super+Q), + "Move focused window to `d`th on the taskbar." + window(d) => (Meta+{d}), - "Open your default terminal." - terminal() => (Super+Enter), - - "Test String" - best_food() => "Hawaiian (Pineapple) Pizza", // ! Akane is italian! You might get in trouble if he sees this! - - "Test integer" - best_number() => 2, - - "Test float" - best_float() => 2.0, - - "List of many values (of different types)" - list_ostuff() => ["apples", "flies", "oranges"] + "How many horns does a unicorn have?" + hornsInUnicorn => 1, } ); -impl Keybinds { - fn test(&self) { - self.bestFloat; - } -} - - -#[derive(Debug, Clone)] -pub struct Keybinds { - variables : HashMap, - keybinds : HashMap, -} - -#[derive(Debug, Deserialize)] -pub struct KeybindsProto { - multitasking: HashMap, -} -lazy_static!{ - pub static ref MULTITASKING : HashMap = { - let h = HashMap::new(); - - h - }; -} - impl ConfigurationSection for Keybinds { - type Raw = KeybindsProto; const PATH : &'static str = "$.keybinds"; - - fn parse(trace: Traceable, raw : &Self::Raw, index: &Index) -> Result>> { - let abs = Self::path(); - let mut errors : Vec> = vec![]; - - println!("Multitasking section!"); - - for (key, value) in raw.multitasking.iter() { - let path = abs.push("multitasking").push(key.clone()); - - let (key_loc, value_loc) = match index.get(&path).unwrap() { - Location::KeyValue(k, v) => (k.loc(),v.loc()), - Location::Value(_) => unreachable!() // Should never reach since we're a hash map. - }; - - let avmacro = match AvMacro::parse( - trace.at_loc(key_loc), key.clone() - ) { - Ok(ok) => { - println!("{:?}", ok); - // Complete expansion process. - }, - Err(er) => { - errors.extend(er); - continue; - } - }; - } - - if errors.len() > 0 { - return Err(errors); - } - - todo!(); - } -} - -#[cfg(test)] -mod tests { - } diff --git a/src/config/sections/mod.rs b/src/config/sections/mod.rs new file mode 100644 index 0000000..d000e1b --- /dev/null +++ b/src/config/sections/mod.rs @@ -0,0 +1 @@ +pub mod keybinds; diff --git a/src/config/sections/section.rs b/src/config/sections/section.rs deleted file mode 100644 index 2f5b766..0000000 --- a/src/config/sections/section.rs +++ /dev/null @@ -1,31 +0,0 @@ -use json_tree::{JSONPath, Index}; - -use crate::{core::error::{TraceableError, Traceable}, config::config::{self, Config}}; - -pub trait ConfigurationSection : Sized { - /// - /// The type the implementing section is built from. - /// Probably a hashmap. - /// - type Raw : Sized; - - /// - /// The absolute path to this section as a str. - /// - const PATH : &'static str; - - /// - /// Returns the absolute path to this section. - /// Can be used in finding location of data. - /// - fn path() -> JSONPath { - Self::PATH.to_string().try_into().unwrap() - } - - fn traceable(key : Option) -> Traceable { - let loc = Config::index().get(&Self::path()).unwrap(); - Traceable::combine(&config::PATH.to_string(), loc, key) - } - - fn parse(trace: Traceable, raw : &Self::Raw, index: &Index) -> Result>>; -} \ No newline at end of file diff --git a/src/config/templating/avvalue.rs b/src/config/templating/avvalue.rs new file mode 100644 index 0000000..8b93f8f --- /dev/null +++ b/src/config/templating/avvalue.rs @@ -0,0 +1,142 @@ +//! A short enum wrapper of the value of a macro in a configuration section +//! +//! Implemented for different types with the macro: `AvValue!([])` +//! the types should have the `AvDeserialize` trait. +//! + +use colored::Colorize; +use compositor_macros::{AvError, description, location, AvValue}; +use serde_json::Value; + +use crate::Nadva::{keyboard::{AvKeys, avkeys::AvKey}, error::{Traceable, TraceableError, AvError}}; + +use super::{AvMacro, r#macro::AvKeysMismatch}; + +/// +/// # UnexpectedType +/// +/// Where the config expects one type, but gets another. +/// +/// ## Members +/// * Location +/// * Expected type +/// * Received type +/// +#[AvError(TraceableError, CONFIG_UNEXPECTED_TYPE, "Config: Unexpected Type")] +pub struct UnexpectedType(Traceable, String, String); + +impl TraceableError for UnexpectedType { + description!(("Expected a {}, got a {}.", &self.1.blue(), &self.2.blue())); + location!(&self.0); +} + +impl UnexpectedType { + fn type_name(v : &Value) -> String { + match v { + Value::Array(_) => "Array", + Value::Object(_) => "Object", + Value::String(_) => "String", + Value::Number(_) => "Number", + Value::Bool(_) => "Boolean", + Value::Null => "Null", + }.to_string() + } + + pub fn from(loc : Traceable, e: &str, v : Value) -> UnexpectedType { + Self(loc, e.to_string(), Self::type_name(&v)) + } +} + +AvValue!([String, i64, f64, bool, AvKeys]); + +impl AvValue { + pub fn parse_same_type(&self, loc: Traceable, val : Value) -> Result> { + + match &self { + AvValue::String(_) => String::deserialize(loc, val), + AvValue::i64(_) => i64::deserialize(loc, val), + AvValue::f64(_) => f64::deserialize(loc, val), + AvValue::bool(_) => bool::deserialize(loc, val), + AvValue::AvKeys(_) => AvKeys::deserialize(loc, val), + } + } + + pub fn consistent_with_macro(&self, loc : Traceable, m: &AvMacro) -> Result<(), Box> { + match &self { + AvValue::String(_) => Ok(()), + AvValue::i64(_) => Ok(()), + AvValue::f64(_) => Ok(()), + AvValue::bool(_) => Ok(()), + AvValue::AvKeys(k) => { + let p : Vec<_> = k.0.iter() + .filter_map(|k| match k { + AvKey::Parameter(k) => Some(k), + _ => None + }) + .collect(); + + + m.has_parameters(p.iter().map(|k| (*k).clone()).collect()) + .map_err(|e| { + Box::new(AvKeysMismatch(loc, k.to_string(), e)) as Box + }) + } + } + } +} + +/// +/// Allows for conversion of `serde_json::Value` to `AvValue` for a given type +/// +pub trait AvDeserialize { + fn deserialize(loc : Traceable, val : Value) -> Result>; +} + +impl AvDeserialize for String { + fn deserialize(loc : Traceable,val : Value) -> Result> { + match val { + Value::String(s) => Ok(AvValue::String(s)), + v => Err(Box::new(UnexpectedType::from(loc, "String", v))) + } + } + +} + +impl AvDeserialize for i64 { + fn deserialize(loc : Traceable, val : Value) -> Result> { + match val { + Value::Number(ref v) => { + match v.as_i64() { + Some(v) => Ok(AvValue::i64(v.clone())), + None => Err(Box::new(UnexpectedType::from(loc, "Integer", Into::::into(v.clone())))) + } + }, + v => Err(Box::new(UnexpectedType::from(loc, "Integer", v.clone()))) + } + } + +} + +impl AvDeserialize for f64 { + fn deserialize(loc : Traceable, val : Value) -> Result> { + match val { + Value::Number(ref v) => { + match v.as_f64() { + Some(v) => Ok(AvValue::f64(v)), + None => Err(Box::new(UnexpectedType::from(loc, "Float",val.clone()))) + } + }, + v => Err(Box::new(UnexpectedType::from(loc, "Float", v))) + } + } +} + +impl AvDeserialize for bool { + fn deserialize(loc : Traceable, val : Value) -> Result> { + match val { + Value::Bool(v) => Ok(AvValue::bool(v)), + v => Err(Box::new(UnexpectedType::from(loc, "Boolean", v))) + } + } + +} \ No newline at end of file diff --git a/src/config/templating/macro.rs b/src/config/templating/macro.rs new file mode 100644 index 0000000..c6520fb --- /dev/null +++ b/src/config/templating/macro.rs @@ -0,0 +1,417 @@ +use std::{collections::HashSet, convert::TryFrom, iter::FromIterator, hash::Hash, }; + +use colored::Colorize; +use compositor_macros::{AvError, description, location}; +use crate::{core::error::{AvError, TraceableError, Traceable}}; + +#[derive(Clone)] +pub enum ParseErrorType { + UnexpectedToken(String), + ExpectedToken(String) +} + +#[AvError(TraceableError, CONFIG_MACRO_PARSE_ERROR, "Config: Macro Parser Error")] +pub struct ParseError { + /// + /// The token that caused the failure. + /// + erroneous: ParseErrorType, + + /// + /// Absolute location of the token. + /// + location: Traceable, +} + +impl TraceableError for ParseError { + location!(&self.location); + + fn description(&self) -> String { + match &self.erroneous { + ParseErrorType::ExpectedToken(t) => { + format!("Expected `{}`", t.blue()) + }, + ParseErrorType::UnexpectedToken(t) => { + format!("Unexpected token `{}`", t.blue()) + } + } + } +} + +/// +/// Parameter types supported by +/// macros. +/// +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum MacroParameter { + /// + /// Digit keys from 0...9 + /// + DigitKey, + + /// + /// Function keys from 1...12 + /// + FunctionKey, +} + +impl<'a> TryFrom<&'a str> for MacroParameter { + type Error = String; + + /// + /// Parse enum code into an enum member. + /// + fn try_from(value: &'a str) -> Result { + match value { + "d" => Ok(Self::DigitKey), + "F" => Ok(Self::FunctionKey), + _ => Err(value.to_string()) + } + } +} + +impl From for String { + fn from(p: MacroParameter) -> Self { + match p { + MacroParameter::DigitKey => "d", + MacroParameter::FunctionKey => "F", + }.to_string() + } +} +impl ToString for MacroParameter { + fn to_string(&self) -> String { + >::into(self.clone()) + } +} + +#[AvError(TraceableError, CONFIG_MACRO_PARAMETER_ERROR, "Config: Macro Parameter Error")] +pub struct ParameterError(pub Traceable, pub String); + +impl TraceableError for ParameterError { + location!(&self.0); + description!(("Invalid macro parameter `{}`", self.1.blue())); +} + +#[AvError(TraceableError, CONFIG_MACRO_EMPTY, "Config: Expected Macro")] +pub struct MacroEmpty(Traceable); + +impl TraceableError for MacroEmpty { + location!(&self.0); + description!(("Expected a macro here, got the silent treatment :(", )); +} + + +/// +/// ## Structure of a Macro +/// All macros have an identifier. +/// Macro identifiers should use `camelCase` +/// and only contain ASCII alphanumeric characters. +/// +/// +/// Macro expressions can either have brackets (for parameters), or not. +/// * With parameters `macroNameHere(param1, param2, ...)` +/// * Without `macroNameHere` (equivalent to `macroNameHere()`) +/// +/// ## Use +/// +/// Allows for macros in the configuration allow for things such as: +/// ```jsonc +/// { +/// "moveToWorkspace1": "Ctrl+Super+1", +/// "moveToWorkspace2": "Ctrl+Super+2", +/// "moveToWorkspace3": "Ctrl+Super+3", +/// /* . . . */ +/// "moveToWorkspace9": "Ctrl+Super+9", +/// } +/// ``` +/// +/// to be shortened to: +/// +/// ```jsonc +/// { +/// "moveToWorkspace(d)": "Ctrl+Super+{d}" +/// } +/// ``` +/// +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct AvMacro { + identifier: String, + parameters: HashSet +} + +impl Hash for AvMacro { + fn hash(&self, state: &mut H) { + self.cannonize().hash(state) + } +} + +#[derive(Clone)] +pub enum Difference { + FromOriginal, + FromNew +} + +impl AvMacro { + /// Two macros are said to have equal signature if + /// they have the same parameters (the order doesn't matter). + pub fn has_signature<'a>(&'a self, o : &'a Self) -> Result<(), (Difference, Vec<&'a MacroParameter>)> { + let u : HashSet<_> = self.parameters.union(&o.parameters).collect(); + match u.len() == self.parameters.len() && u.len() == o.parameters.len() { + true => Ok(()), + false => { + let from_original : Vec<_> = self.parameters.difference(&o.parameters).collect(); + if from_original.len() > 0 { + return Err((Difference::FromOriginal, from_original)) + } + + return Err(( + Difference::FromNew, + o.parameters.difference(&self.parameters).collect() + )) + + + + } + } + } + + pub fn has_parameters(&self, v : Vec) -> Result<(), (Difference, Vec)> { + match self.parameters.len() { + l if l < v.len() => { + Err( + (Difference::FromNew, + v.iter().filter(|k| !self.parameters.contains(k)) + .map(|k| k.clone()) + .collect() + ) + ) + }, + l if l == v.len() => Ok(()), + l if l > v.len() => { + Err( + (Difference::FromNew, + self.parameters.iter().filter(|k| !v.contains(k)) + .map(|k| k.clone()) + .collect() + ) + ) + }, + _ => unreachable!() + } + } + + pub fn has_same_id(&self, o : &Self) -> bool { + self.identifier == o.identifier + } + + pub fn identifier(&self) -> String { + self.identifier.clone() + } + + fn cannonize(&self) -> String { + format!("{}{:?}", self.identifier, self.parameters) + } + pub fn parse(loc : Traceable, value : String) -> Result>> { + let mut ident = String::new(); + let mut parameters : Vec = vec![]; + let mut parameter_locs : Vec = vec![]; + + enum State { + Identifier, + Parameters, + Finished + } + + let mut current_token = "".to_string(); + + let mut current_state = State::Identifier; + + // If this macro is empty. + if value.len() == 0 { + return Err( + vec![Box::new(MacroEmpty(loc))] + ); + } + + // Parse each character. + for (index, chr) in value.chars().enumerate() { + if chr == ' ' { + continue; + } + match current_state { + State::Identifier => match chr { + c if c.is_ascii_alphanumeric() => { + current_token.push(c); + }, + '(' => { + ident = current_token; + current_token = "".to_string(); + + parameter_locs.push(loc.at_index(index + 1)); + + current_state = State::Parameters; + }, + + _ => return Err( + vec![Box::new(ParseError { + location: loc.at_index(index), + erroneous: ParseErrorType::UnexpectedToken(chr.to_string()), + })] + ) + }, + State::Parameters => match chr { + c if c.is_ascii_alphanumeric() => { + current_token.push(c); + }, + ',' => { + parameters.push(current_token); + parameter_locs.push(loc.at_index(index + 1)); + + current_token = "".to_string(); + + }, + ')' => { + parameter_locs.push(loc.at_index(index + 1)); + parameters.push(current_token); + current_token = "".to_string(); + + current_state = State::Finished; + + } + _ => return Err( + vec![Box::new(ParseError { + location: loc.at_index(index), + erroneous: ParseErrorType::UnexpectedToken(chr.to_string()), + })] + ) + }, + State::Finished => { + return Err( + vec![Box::new(ParseError { + location: loc.at_index(index), + erroneous: ParseErrorType::ExpectedToken("".to_string()), + })] + ) + } + } + } + + match current_state { + State::Identifier => { + ident = current_token; + }, + State::Parameters => { + // Didn't finish the parameters with a ')' + return Err( + vec![Box::new(ParseError { + location: loc.at_index(value.len()), + erroneous: ParseErrorType::ExpectedToken(")".to_string()), + })] + ) + }, + _ => {} + } + + let parameters = parameters.iter() + .map(|v| MacroParameter::try_from(v.as_str())); + + let errs : Vec<_> = parameters.clone() + .enumerate() + .filter(|(_, r)| r.is_err()) + .map(|(i, r)| { + let err = r.unwrap_err(); + let e = parameter_locs.get(i).unwrap(); + + Box::new( + ParameterError(e.clone(), err) + ) as Box + }) + .collect(); + + + if errs.len() > 0 { + return Err(errs); + } + + let parameters : Vec<_> = parameters + .map(|r| r.unwrap()) + .collect(); + + Ok( + Self { + identifier: ident, + parameters: HashSet::from_iter(parameters.iter().map(|s| s.clone())) + } + ) + } +} + +#[AvError(TraceableError, CONFIG_MACRO_SIGNATURE_MISMATCH, "Config: Macro Signature Mismatch")] +pub struct SignatureMismatchError(pub Traceable, pub String, pub (Difference, Vec)); + +impl TraceableError for SignatureMismatchError { + location!(&self.0); + description!( + ( + "The macro parameter(s) of {} is not valid against its definition.\n\ + {}", + self.1, + match &self.2 { + (p, v) => format!( + "{} {}", + match p { + Difference::FromNew => "Excess parameters:", + Difference::FromOriginal => "Missing:" + }, + v.iter().map(|s| + format!("`{}` ", s.to_string().blue() + ) + ).collect::()), + } + ) + ); +} +#[AvError(TraceableError, CONFIG_AVKEYS_PARAMETER_MISMATCH, "Config: Macro Value Mismatch")] +pub struct AvKeysMismatch(pub Traceable, pub String, pub (Difference, Vec)); + +impl TraceableError for AvKeysMismatch { + location!(&self.0); + description!( + ( + "The macro parameter(s) of the Key expression {} are not valid against the macro holding it.\n\ + {}", + self.1, + match &self.2 { + (p, v) => format!( + "{} {}", + match p { + Difference::FromNew => "Excess parameters:", + Difference::FromOriginal => "Missing:" + }, + v.iter().map(|s| + format!("`{}` ", s.to_string().blue() + ) + ).collect::()), + } + ) + ); +} + +#[cfg(test)] +mod tests { + use compositor_macros::traceable; + + use super::AvMacro; + + #[test] + fn parsing_test() { + let m = AvMacro::parse( + traceable!(), + "helpMe(deez)".to_string() + ); + + let m = m.unwrap(); + + println!("{m:?}"); + } +} \ No newline at end of file diff --git a/src/config/templating/mod.rs b/src/config/templating/mod.rs new file mode 100644 index 0000000..0fbe6bb --- /dev/null +++ b/src/config/templating/mod.rs @@ -0,0 +1,6 @@ +pub mod r#macro; +pub mod avvalue; +pub use r#macro::AvMacro; +pub use r#macro::MacroParameter; +pub use avvalue::AvDeserialize; +pub use avvalue::AvValue; \ No newline at end of file diff --git a/src/consts.rs b/src/consts.rs index 9d69cbc..d41739a 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -4,5 +4,5 @@ use lazy_static::lazy_static; lazy_static! { pub static ref CONFIG_FOLDER : &'static Path = Path::new("/etc/AvdanOS"); - pub static ref CONFIG_FILE : &'static Path = Path::new("Compositor.json"); + pub static ref CONFIG_FILE : &'static Path = Path::new("Compositor.jsonc"); } diff --git a/src/core/config.rs b/src/core/config.rs deleted file mode 100644 index 4d8e970..0000000 --- a/src/core/config.rs +++ /dev/null @@ -1,128 +0,0 @@ -#![allow(dead_code)] -use std::{ - error::Error, - fs::{ - self, - File, - }, - io::BufReader, -}; - -use regex::{ - Regex, - Captures, -}; - -use serde::Deserialize; - -use lazy_static::lazy_static; - -use json_comments::StripComments; - -use crate::CONST::{ - CONFIG_FOLDER, - CONFIG_FILE, -}; - -#[derive(Deserialize, Debug)] -pub struct Config { - test: String, -} - -impl Config { - pub fn from_file() -> Result> { - fs::create_dir_all(*CONFIG_FOLDER) - .expect("Error while creating the AvdanOS config directory!"); - - let file: File = fs::OpenOptions::new() - .read(true).write(true).create(true) - .open(CONFIG_FOLDER.join(*CONFIG_FILE))?; - - let reader: BufReader = BufReader::new(file); - - let stripped: StripComments> = StripComments::new(reader); - - let parsed: Config = serde_json::from_reader(stripped)?; - Ok(parsed) - } -} - -#[derive(Debug)] -struct TemplateString { - raw: String, - tokens: Vec, -} - -impl<'de> TemplateString { - - fn from_raw_string<'a> ( - raw_string: String, - ) -> Result { - - lazy_static! { - static ref VARIABLES_REGEX : Regex = Regex::new(r"\{(.*?)\}").unwrap(); - } - - // Check for a brace - {} - mismatch - let braces_count: [usize; 2] = ["{", "}"] - .map(|c: &str| raw_string.matches(c).count()); - - if braces_count[0] != braces_count[1] { - return Err("Brace {} mismatch !"); - } - - let variables : Vec = VARIABLES_REGEX - .captures_iter(&raw_string) - .map(|m: Captures| - m.get(1).unwrap().as_str().to_string() - ) - .collect(); - - Ok ( - Self { - raw: raw_string, - tokens : variables - } - ) - } -} - -impl<'de> Deserialize<'de> for TemplateString { - fn deserialize(deserializer: D) -> Result - where D: serde::Deserializer<'de> { - let raw_string : String = String::deserialize(deserializer)?; - Self::from_raw_string(raw_string).map_err(serde::de::Error::custom) - } -} - -#[cfg(test)] -mod tests { - use super::TemplateString; - - #[test] - fn test_variable_extract() { - let template: Result = TemplateString::from_raw_string ( - "Logo+{a}+{b}+{c}".to_string() - ); - - assert!(template.is_ok()); - - let template: TemplateString = template.unwrap(); - - assert_eq! ( - template.tokens, vec!["a", "b", "c"] - ); - - } - - #[test] - fn test_braces_mismatch() { - let template: Result = TemplateString::from_raw_string( - "Logo+{n}}".to_string() - ); - - assert!(template.is_err()); - - println!("{}", template.unwrap_err()); - } -} diff --git a/src/core/error.rs b/src/core/error.rs new file mode 100644 index 0000000..a98b31e --- /dev/null +++ b/src/core/error.rs @@ -0,0 +1,345 @@ +use std::{cmp::Ordering}; +use colored::Colorize; +use json_tree::{Location}; + + +pub mod color { + pub const ERROR : colored::Color = colored::Color::TrueColor { + r: 215, + g: 38, + b: 56 + }; + + pub const WARNING : colored::Color = colored::Color::TrueColor { + r: 246, + g: 170, + b: 28 + }; + + pub const NEUTRAL : colored::Color = colored::Color::TrueColor { + r: 5, + g:142, + b: 217 + }; +} + +pub trait Indentable + where Self: Sized +{ + const BASE_UNIT : usize = 2; + /// + /// Indent all the lines by a specific number of spaces + /// + fn indent(&self, levels: usize) -> String; +} + +impl Indentable for String { + fn indent(&self, level: usize) -> String { + let v : Vec<_> = self + .split("\n") + .map(|s| format!("{}{}", " ".repeat(Self::BASE_UNIT * level), s)) + .collect(); + v + .join("\n") + + } +} + +impl Indentable for &str { + fn indent(&self, level: usize) -> String { + let v : Vec<_> = self + .split("\n") + .map(|s| format!("{}{}", " ".repeat(Self::BASE_UNIT * level), s)) + .collect(); + v + .join("\n") + } +} + + +/// +/// ## Errors +/// +/// All errors in AvdanOS should implement this trait. +/// +/// `AvError`s allow for colorful, and descriptive error messages. +/// +/// The `#[AvError]` macro can be used to make the process of creating +/// a new error easily. +/// +/// #### Example - Generic Error +/// +/// ```rust +/// use crate::core::error::AvError; +/// use compositor_macros::AvError; +/// // ↓ Error code ↓ Error Title +/// #[AvError(EXAMPLE_ERROR, "Example Error for Documentation")] +/// struct ExampleError; +/// +/// let example = ExampleError; +/// println!("{}", example); +/// +/// ``` +pub trait AvError : std::fmt::Display + std::fmt::Debug + std::error::Error { + + + /// + /// The unique code for this error. + /// + /// Convention is to use TRAIN_CASE + /// + /// ### Examples + /// ``` + /// "CONFIG_MACRO_NOT_FOUND" + /// "CONFIG_MACRO_INVALID_PARAMETER" + /// ``` + /// + fn code(&self) -> String; + + /// + /// A title for this error (shared by + /// all instances of this error). + /// + /// Capitalize each word. + /// + /// ### Examples + /// ``` + /// "Macro Not Found" + /// "Invalid Macro Parameter" + /// ``` + /// + fn title(&self) -> String; + + + /// + /// The content of this error warning. + /// + /// **NOTE**: do not override this, unless + /// you are making a new error type, + /// like `LocatableError` + /// + fn body(&self) -> String { + "".into() + } +} + + +/// +/// Represents a location +/// of a traceable error. +/// +#[derive(Debug, Clone)] +pub struct Traceable { + /// + /// The path to the file + /// that caused this error. + /// + path : String, + + /// + /// The line where this error + /// occurred. + /// + line : usize, + + /// + /// The column index where this + /// error occurred. + /// + column : usize, +} + +impl Traceable { + /// + /// Makes a new instance of [`Traceable`]. + /// + pub fn new(path: String, loc : (usize, usize)) -> Self { + Self { + path, + line: loc.0, + column: loc.1 + } + } + + /// + /// Returns new [`Traceable`] at a string index + /// relative to the current column pos. + /// + pub fn at_index(&self, index: usize) -> Self { + Self { + path : self.path.clone(), + line : self.line, + column : self.column + 1 + index + // Account for the "" ^ + } + } + + /// + /// Returns a new [`Traceable`] with new + /// line and column numbers. + /// + pub fn at_loc(&self, (line, column): (usize, usize)) -> Self { + Self { + path: self.path.clone(), + line, + column + } + } + + /// + /// Makes a Locatable from a Location and a file path + /// + /// key object determines if it should return the Locatable for the key or value + pub fn combine(path: &String, loc: &Location, key: Option) -> Self { + let (line, column) = match loc { + Location::KeyValue(k, v) => match key { + None => unimplemented!("Should pass in Some(bool) as last argument for KeyValue pairs"), + Some(true) => k, + Some(false) => v, + }, + Location::Value(v) => v + }.loc(); + + Self { + path : path.clone(), + line, + column + } + } +} + + +impl ToString for Traceable { + fn to_string(&self) -> String { + format!("{}:{}:{}", self.path, self.line, self.column) + } +} + +/// +/// ## Traceable Error +/// Traceable Errors are errors that can be +/// traced to a specific location +/// in a file such as a mis-configuration. +/// +/// ### Macros +/// +/// * `location!(&self )` +/// - Implements [`TraceableError::location`] by returning the value +/// inside the brackets +/// +/// See [`location`]. +/// +/// +/// * `description!(())` +/// - Implements [`TraceableError::description`] by returning `format!()` +/// which allows for easy string interpolation. +/// +/// See [`description`]. +/// +/// ### Example +/// +/// ```rust +/// use crate::core::error::{LocatableError, Traceable}; +/// use compositor_macros::{AvError, location, description}; +/// +/// #[AvError(TraceableError, EXAMPLE_ERROR, "Example Error for Documentation")] +/// struct ExampleError(Traceable); +/// +/// +/// +/// impl TraceableError for ExampleError { +/// location!(&self.0); +/// description!(("Example traceable error", )); +/// } +/// +/// let test = ExampleError( +/// Traceable { +/// path: "config.txt".to_string(), +/// line: 203, +/// column: 20 +/// } +/// ); +/// +/// println!("{}", test); +/// +/// ``` +/// +pub trait TraceableError : AvError { + /// + /// The error's location + /// + fn location(&self) -> &Traceable; + + fn description(&self) -> String; + + fn body(&self) -> String { + format!( + "{}\nat {}\n", + TraceableError::description(self), + self.location().to_string().color(color::NEUTRAL) + ) + } + + +} + +pub fn compare_errors(s : &Box, o: &Box) + -> Option { + let s = s.location(); + let o = o.location(); + + if s.path != o.path { + return None; + } + + match s.line.partial_cmp(&o.line).unwrap() { + Ordering::Equal => s.column.partial_cmp(&o.column), + a => Some(a) + } +} + +#[cfg(test)] +mod tests { + use compositor_macros::AvError; + use crate::core::error::{TraceableError, Traceable, }; + + use super::AvError; + + #[test] + fn derive() { + #[AvError(TEST_DERIVE_ERROR, "Test Error")] + struct Error; + + let test = Error; + println!("{}", test); + } + + #[test] + fn locatable() { + #[AvError(TraceableError, TEST_DERIVE_ERROR, "Test Error")] + struct Locatable(Traceable); + + + let test = Locatable( + Traceable { + path: "src/core/error.rs".to_string(), + line: 203, + column: 20 + } + ); + + impl TraceableError for Locatable { + fn location(&self) -> &Traceable { + &self.0 + } + + fn description(&self) -> String { + "Test Traceable thing!".into() + } + } + + + + println!("{}", test); + } +} \ No newline at end of file diff --git a/src/core/keyboard/avkeys.rs b/src/core/keyboard/avkeys.rs new file mode 100644 index 0000000..96a4b35 --- /dev/null +++ b/src/core/keyboard/avkeys.rs @@ -0,0 +1,148 @@ +use std::convert::TryFrom; + +use colored::Colorize; +use compositor_macros::{AvError, location, description}; +use serde_json::Value; + +use crate::{ + config::templating::{ + MacroParameter, + avvalue::{ + UnexpectedType, AvValue + }, + r#macro::ParameterError, + AvDeserialize + }, + core::error::{ + AvError, + }, + Nadva::error::{ + TraceableError, + Traceable + } +}; + +#[derive(Debug, PartialEq, Clone)] +pub enum AvKey { + Key(String), + Parameter(MacroParameter) +} + +impl TryFrom for AvKey { + type Error = (String, String); + + fn try_from(value: String) -> Result { + match value.as_str() { + "" => Err(("key".to_string(), "".to_string())), + v if v.ends_with("}") && v.starts_with("{") => { + let t = &v[1..(v.len() - 1)]; + return Ok(AvKey::Parameter( + MacroParameter::try_from(t) + .map_err(|s| ("macro_param".to_string(), s))? + )) + }, + a => Ok(AvKey::Key(a.to_string())) + } + } +} + +impl ToString for AvKey { + fn to_string(&self) -> String { + match self { + Self::Key(k) => k.clone(), + Self::Parameter(p) => format!("{{{}}}", p.to_string()) + } + } +} + +#[derive(Debug, PartialEq,)] +pub struct AvKeys(pub Vec); + +impl TryFrom for AvKeys { + type Error = (String, String); + + fn try_from(value: String) -> Result { + let vec : Vec<_> = value + .split("+") + .map(|s| AvKey::try_from(s.to_string())) + .collect(); + + + let mut v = vec![]; + for val in vec { + v.push(val?); + } + + Ok(Self(v)) + } +} + +impl Clone for AvKeys { + fn clone(&self) -> Self { + Self( + self.0.iter().map(|k| (*k).clone()).collect() + ) + } +} + +impl ToString for AvKeys { + fn to_string(&self) -> String { + let v : Vec<_> = self.0.iter().map(AvKey::to_string).collect(); + + v.join("+") + } +} + +#[AvError(TraceableError, CONFIG_KEY_PARSE, "Config: Keyboard Shortcut Parsing Error")] +struct KeyParseError(Traceable, String); + +impl TraceableError for KeyParseError { + location!(&self.0); + description!(("Error while parsing `{}` as a Keyboard Key", self.1.blue())); +} + +impl AvDeserialize for AvKeys { + fn deserialize(loc : Traceable, val : serde_json::Value) -> Result> { + match val { + Value::String(s) => Ok( + AvValue::AvKeys( + AvKeys::try_from(s) + .map_err(|(t, v)| + match t.as_str() { + "key" => Box::new(KeyParseError(loc, v)) as Box, + "macro_param" => Box::new(ParameterError(loc, v)) as Box, + _ => unreachable!() + } + )? + ) + ), + v => Err(Box::new(UnexpectedType::from(loc, "String", v))) + } + } +} + +impl Default for AvKeys { + fn default() -> Self { + Self(vec![]) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryFrom; + + use crate::config::templating::MacroParameter; + + use super::{AvKeys, AvKey}; + + + #[test] + fn deserialize() { + let j = "Ctrl+{d}".to_string(); + + let v = AvKeys::try_from(j); + + assert_eq!(v, Ok(AvKeys(vec![AvKey::Key("Ctrl".to_string()), AvKey::Parameter(MacroParameter::DigitKey)]))) + + } +} \ No newline at end of file diff --git a/src/core/keyboard/mod.rs b/src/core/keyboard/mod.rs new file mode 100644 index 0000000..612e46d --- /dev/null +++ b/src/core/keyboard/mod.rs @@ -0,0 +1,3 @@ +pub mod avkeys; +pub use avkeys::AvKeys; +pub use avkeys::AvKey; \ No newline at end of file diff --git a/src/core/mod.rs b/src/core/mod.rs index d23d10a..26cac3c 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,3 +1,2 @@ -pub mod config; - -pub use config::Config; +pub mod error; +pub mod keyboard; \ No newline at end of file diff --git a/src/macros/Cargo.toml b/src/macros/Cargo.toml new file mode 100644 index 0000000..bb24028 --- /dev/null +++ b/src/macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "compositor-macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0.21" +proc-macro2 = "1.0.43" +syn = {version = "1.0.99", features = ["full"]} \ No newline at end of file diff --git a/src/macros/src/avvalue.rs b/src/macros/src/avvalue.rs new file mode 100644 index 0000000..61f94a3 --- /dev/null +++ b/src/macros/src/avvalue.rs @@ -0,0 +1,145 @@ +use proc_macro2::TokenStream; +use syn::{Token, parse::ParseStream, Error}; +use quote::quote; + +pub enum AvValue { + String(syn::LitStr), + Integer(syn::LitInt), + Float(syn::LitFloat), + Null(syn::Ident), + Boolean(syn::LitBool), + AvKeys(syn::punctuated::Punctuated), + List(syn::punctuated::Punctuated) +} + +pub enum AvKey { + Key(syn::Ident), + Parameter(syn::token::Brace, syn::Ident) +} + +impl AvValue { + pub fn get_type(&self) -> TokenStream { + use AvValue::*; + match self { + String(_) => quote!{ String }, + Integer(_) => quote!{ i64 }, + Float(_) => quote! { f64 }, + Null(_) => panic!("Null token is not supported for deserialization!"), + Boolean(_) => quote! { bool }, + AvKeys(_) => quote! { AvKeys }, + List(_) => panic!("List tokens are not supported for deserialization yet!"), + } + } + + pub fn value(&self) -> TokenStream { + let t = self.get_type(); + let v = match self { + AvValue::String(s) => quote! { #s.into() }, + AvValue::Integer(s) => quote! { #s.into() }, + AvValue::Float(s) => quote! { #s.into() }, + AvValue::Null(_) => panic!("Null token is not supported for deserialization!"), + AvValue::Boolean(s) => quote! { #s.into() }, + AvValue::AvKeys(s) => { + let t = s.iter().map(|k| { + match k { + AvKey::Key(k) => { + let k = k.to_string(); + quote!{ + AvKey::Key(#k.into()) + } + }, + AvKey::Parameter(_, p) => { + let p = p.to_string(); + quote!{ + AvKey::Parameter(#p.try_into().unwrap()) + } + }, + } + }); + + quote!{ + AvKeys( + vec![#(#t),*] + ) + } + }, + AvValue::List(_) => panic!("List tokens are not supported for deserialization yet!"), + }; + + quote! {AvValue::#t(#v)} + } +} + +impl syn::parse::Parse for AvKey { + fn parse(input: ParseStream) -> syn::Result { + let look = input.lookahead1(); + return match look.peek(syn::Ident) { + true => { + Ok( + AvKey::Key(input.parse()?) + ) + }, + false => { + let content; + + Ok( + AvKey::Parameter( + syn::braced!(content in input), + content.parse()? + ) + ) + } + }; + } +} + +impl syn::parse::Parse for AvValue { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + if input.peek(syn::LitBool) { + return Ok(Self::Boolean(input.parse()?)); + } + + if input.peek(syn::LitInt) { + return Ok(Self::Integer(input.parse()?)); + } + + if input.peek(syn::LitFloat) { + return Ok(Self::Float(input.parse()?)); + } + + if input.peek(syn::LitStr) { + return Ok(Self::String(input.parse()?)); + } + + if input.peek(syn::Ident) { + let ident : syn::Ident = input.parse()?; + if ident.to_string().eq("null") { + return Ok(Self::Null(ident)); + } + } + + if input.peek(syn::token::Paren) { + let content; + + let _ = syn::parenthesized!(content in input); + + return Ok(Self::AvKeys(content.parse_terminated(AvKey::parse)?)) + } + + if input.peek(syn::token::Bracket) { + let content; + + let _ = syn::bracketed!(content in input); + + return Ok(Self::List(content.parse_terminated(AvValue::parse)?)); + } + + // let mut e = Diagnostic::new(Level::Error, "Expected a bool, float/int, string, null, or AvKey collection."); + // e.set_spans(input.span().unwrap()); + // e.emit(); + + Err( + Error::new(input.span(), "Expected a bool, float/int, string, null, or AvKey collection.") + ) + } +} \ No newline at end of file diff --git a/src/macros/src/delcaration.rs b/src/macros/src/delcaration.rs new file mode 100644 index 0000000..d56179f --- /dev/null +++ b/src/macros/src/delcaration.rs @@ -0,0 +1,109 @@ +use std::fmt::{Debug}; + +use quote::ToTokens; +use syn::{punctuated::Punctuated, token::{Brace}, Token, Ident, parse::{Parse, ParseStream}, braced}; + +use crate::avvalue::AvValue; + +pub struct ConfigDelcaration { + pub ident : Ident, + _brace_token: Brace, + pub fields : Punctuated +} + +impl Parse for ConfigDelcaration { + fn parse(input: ParseStream) -> syn::Result { + let content; + Ok(ConfigDelcaration { + ident : input.parse()?, + _brace_token: braced!(content in input), + fields: content.parse_terminated(AvMacro::parse)?, + }) + } +} + +pub struct AvMacro { + // Comment for users + description: syn::LitStr, + + // Macro declaration itself with its parameters + avmacro : (String, Vec), + + // Separates the macro + _delim : Token![=>], + + default : AvValue, +} + +impl AvMacro { + pub fn description(&self) -> String { + self.description.value() + } + + pub fn default(&self) -> &AvValue { + &self.default + } + + pub fn av_macro(&self) -> (String, Vec) { + self.avmacro.clone() + } +} + +impl Parse for AvMacro { + fn parse(input: ParseStream) -> syn::Result { + Ok(AvMacro { + description: input.parse()?, + avmacro : { + match input.peek2(Token![=>]) { + true => { + // Macro has no params. + let ident : syn::Ident = input.parse()?; + (ident.to_string(), vec![]) + }, + false => { + let exp : syn::ExprCall = input.parse()?; + ( + exp.func.to_token_stream().to_string(), + exp.args.into_iter().map(|t| t.to_token_stream().to_string()).collect() + ) + } + } + }, + _delim: input.parse()?, + default : input.parse()? + }) + } +} + +impl Debug for AvMacro { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, + "AvMacro Declaration:\n Name: {}\n Parameters:\n", + &self.avmacro.0, + )?; + + for param in (&self.avmacro.1).into_iter() { + let n = param.to_token_stream().to_string(); + write!(f, " {n}\n")?; + } + + write!(f, "\n") + } +} + + + + +impl Debug for ConfigDelcaration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ConfigDeclaration:\n")?; + + for field in (&self.fields).into_iter() { + write!(f, " {:?}", field)?; + } + + Ok(()) + } +} + + diff --git a/src/macros/src/lib.rs b/src/macros/src/lib.rs index 7a3a966..c7cc944 100644 --- a/src/macros/src/lib.rs +++ b/src/macros/src/lib.rs @@ -1,19 +1,84 @@ #![feature(proc_macro_diagnostic)] -use std::fs; - mod delcaration; mod avvalue; -use avvalue::AvValue; use delcaration::ConfigDelcaration; use proc_macro::{Diagnostic, Level,}; use proc_macro2::TokenStream; -use quote::{quote, quote_spanned, TokenStreamExt}; -use syn::{parse_macro_input, AttributeArgs, ItemStruct, LitStr}; +use quote::{quote, }; +use syn::{parse_macro_input, AttributeArgs, ItemStruct, LitStr, punctuated::Punctuated, Token, bracketed, }; + +#[proc_macro] +#[allow(non_snake_case)] +pub fn AvValue(input : proc_macro::TokenStream) -> proc_macro::TokenStream { + struct AvValueDeclaration { + types : Punctuated + } + + impl syn::parse::Parse for AvValueDeclaration { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + + let _ = bracketed!(content in input); + Ok(Self { + types: content.parse_terminated(syn::TypePath::parse)? + }) + } + } + + let t = parse_macro_input!(input as AvValueDeclaration); + + let iter = t.types.iter(); + let variants = iter.clone().map(|t| { + quote!{ + #t(#t) + } + }); + + let impls = iter; + + + let impls = impls.map(|t| { + quote!{ + impl<'a> core::convert::TryFrom<&'a AvValue> for #t { + type Error = (); + + fn try_from(value: &'a AvValue) -> Result { + match value { + AvValue::#t(k) => Ok(k.clone()), + _ => Err(()) + } + } + } + } + }); + + + quote!{ + #[derive(Debug, PartialEq,)] + #[allow(non_camel_case_types)] + pub enum AvValue { + #(#variants),* + } + + #(#impls)* + }.into() +} + +#[proc_macro] +pub fn traceable(_ : proc_macro::TokenStream) -> proc_macro::TokenStream { + + quote! { + crate::core::error::Traceable::new( + file!().to_string(), + (line!() as usize, column!() as usize) + ) + }.into() +} #[proc_macro] -pub fn export_test(struc: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn config_section(struc: proc_macro::TokenStream) -> proc_macro::TokenStream { let parsed = parse_macro_input!(struc as ConfigDelcaration); // fs::write("./test.txt", format!("{:?}", parsed)).unwrap(); @@ -22,9 +87,11 @@ pub fn export_test(struc: proc_macro::TokenStream) -> proc_macro::TokenStream { let mut q = quote! {}; - for f in parsed.fields.iter() { - let (name, params) = f.av_macro(); - let comments = f.description(); + + let iter = parsed.fields.iter(); + for field in iter.clone() { + let (name, _) = field.av_macro(); + let comments = field.description(); let n = syn::Ident::new(&name, ident.span()); @@ -38,44 +105,87 @@ pub fn export_test(struc: proc_macro::TokenStream) -> proc_macro::TokenStream { } } - let typ : TokenStream = match f.default() { - AvValue::String(_) => quote!{ String }, - AvValue::Integer(_) => quote!{ i64 }, - AvValue::Float(_) => quote! { f64 }, - AvValue::Null(_) => panic!("Null token is not supported for deserialization!"), - AvValue::Boolean(_) => quote! { bool }, - AvValue::AvKeys(_) => quote! { crate::core::keyboard::AvKeys }, - AvValue::List(_) => quote! { serde_json::value::Value }, - }; + let typ : TokenStream = field.default().get_type(); let z = quote! { #q #lines - #n : #typ, + pub #n : #typ, }; q = z; } + + let macro_registration = iter.clone() + .map(|m| { + let v = m.default().value(); + let (m_ident, m_params) = m.av_macro(); + let av_mac_raw = format!("{m_ident}{}", match m_params.len() { + 0 => format!(""), + _ => format!("({})", m_params.join(",")) + }); + + quote!{ + let m = AvMacro::parse( + traceable!(), + // Insert full macro as string + #av_mac_raw.to_string() + ).unwrap(); + declared.insert( + m.clone(), + #v + ); + + ids.insert(#m_ident, m); + } + }); + + let macro_idents = iter.map(|m| m.av_macro().0) + .map(|k| { + let n = syn::Ident::new(&k, ident.span()); + quote! { + #n: m.get(ids.get(#k).unwrap()).unwrap().try_into().unwrap(), + } + }); + quote! { + use compositor_macros::traceable; + use std::collections::HashMap; + + use crate::core::keyboard::{AvKeys, AvKey}; + use crate::config::{ + templating::{AvValue, AvMacro} + }; + #[allow(non_snake_case)] - struct #ident { + #[derive(Debug)] + pub struct #ident { #q } - impl<'de> serde::Deserialize<'de> for #ident { + impl<'de> serde::de::Deserialize<'de> for #ident { fn deserialize(deserializer: D) -> Result - where D: serde::Deserializer<'de> + where D: serde::Deserializer<'de> { - use std::collections::HashMap; - use serde::de::Error; - use serde_json::Value; - - let v : HashMap = - serde::Deserialize::deserialize(deserializer)?; - - todo!(); + let mut ids : HashMap<&str, AvMacro> = HashMap::new(); + let declared = { + let mut declared : HashMap = HashMap::new(); + + #(#macro_registration)* + + declared + }; + let raw : HashMap = serde::de::Deserialize::deserialize(deserializer)?; + + let m = ::from_map(declared, raw); + + Ok(Keybinds { + #(#macro_idents)* + }) } } + + }.into() } @@ -94,6 +204,7 @@ extern crate proc_macro; /// /// #[proc_macro_attribute] +#[allow(non_snake_case)] pub fn AvError(attributes : proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream { let a = parse_macro_input!(attributes as AttributeArgs); @@ -176,18 +287,18 @@ pub fn AvError(attributes : proc_macro::TokenStream, input: proc_macro::TokenStr let err_type = match err_type { syn::NestedMeta::Meta(l) => l, _ => { - return quote::quote! { + (return quote::quote! { compile_error!("`ERROR TYPE` needs to be a raw identifier (no ::)") - }.into(); + }.into()); } }; let err_type = match err_type { syn::Meta::Path(tkn) => tkn, _ => { - return quote::quote! { + (return quote::quote! { compile_error!("Expected `ERROR TYPE` to be a Trait!") - }.into(); + }.into()); } }; @@ -203,6 +314,7 @@ pub fn AvError(attributes : proc_macro::TokenStream, input: proc_macro::TokenStr }; quote! { + #[derive(Clone)] #input impl AvError for #ident { @@ -240,6 +352,7 @@ pub fn AvError(attributes : proc_macro::TokenStream, input: proc_macro::TokenStr } } + impl std::error::Error for #ident {} }.into() } @@ -273,6 +386,16 @@ pub fn description(attrs : proc_macro::TokenStream) -> proc_macro::TokenStream { }.into() } +#[proc_macro] +pub fn name(attrs: proc_macro::TokenStream) -> proc_macro::TokenStream { + let args = parse_macro_input!(attrs as syn::LitStr); + quote! { + fn name<'a>(&self) -> &'a str { + #args + } + }.into() +} + /// /// ## TraceableError Location /// Generates a TraceableError::Location implementation for diff --git a/src/main.rs b/src/main.rs index 0f0f540..945e5b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,9 @@ pub mod core; mod consts; use crate::consts as CONST; -use crate::core as Nadva; +pub(crate) use crate::config::Config; +pub(crate) use crate::core as Nadva; + mod grabs; mod handler; @@ -31,7 +33,18 @@ pub struct CalloopData { display: Display, } +pub mod config; + fn main() -> Result<(), Box> { + println!(); + println!(); + // Load Nadva's Config + Config::load().unwrap(); + + println!("window {:?}", &Config::config().keybinds.window); + println!(); + println!(); + { let log: Logger = ::slog::Logger::root(::slog_stdlog::StdLog.fuse(), slog::o!()); slog_stdlog::init()?; @@ -61,10 +74,6 @@ fn main() -> Result<(), Box> { event_loop.run(None, &mut data, move |_| { /* The compositor is running */ })?; } - let config = Nadva::Config::from_file() - .unwrap(); - - println!("{config:?}"); - + Ok(()) }