From 7c5bec7db2a2fc8c796d5f31bdeb03da0946133d Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 19 Feb 2023 13:49:41 +0100 Subject: feat: add project & registry parsing /w commands --- src/command.rs | 263 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 src/command.rs (limited to 'src/command.rs') diff --git a/src/command.rs b/src/command.rs new file mode 100644 index 0000000..c7ada95 --- /dev/null +++ b/src/command.rs @@ -0,0 +1,263 @@ +//! OpenGL command. +use crate::xml::element::{Elements, FromElements}; + +/// A command. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Command +{ + prototype: Prototype, + parameters: Vec, +} + +impl Command +{ + /// Returns a new `Command`. + pub fn new( + prototype: Prototype, + parameters: impl IntoIterator, + ) -> Self + { + Self { + prototype, + parameters: parameters.into_iter().collect(), + } + } + + /// Returns the command prototype. + #[must_use] + pub fn prototype(&self) -> &Prototype + { + &self.prototype + } + + /// Returns the command parameters. + #[must_use] + pub fn parameters(&self) -> &[Parameter] + { + &self.parameters + } +} + +impl FromElements for Command +{ + type Error = Error; + + fn from_elements( + elements: &crate::xml::element::Elements, + ) -> Result + { + let proto_element = elements + .get_first_tagged_element("proto") + .ok_or(Self::Error::MissingPrototype)?; + + let prototype = Prototype::from_elements(proto_element.child_elements())?; + + let parameters = elements + .get_all_tagged_elements_with_name("param") + .into_iter() + .map(|param_element| Parameter::from_elements(param_element.child_elements())) + .collect::, _>>()?; + + Ok(Self { + prototype, + parameters, + }) + } +} + +/// [`Command`] error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + /// No 'proto' element was found. + #[error("No 'proto' element was found")] + MissingPrototype, + + /// Invalid prototype. + #[error("Invalid prototype")] + InvalidPrototype(#[from] PrototypeError), + + /// Invalid parameter. + #[error("Invalid parameter")] + InvalidParameter(#[from] ParameterError), +} + +/// A command prototype. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Prototype +{ + name: String, + return_type: String, +} + +impl Prototype +{ + /// Returns a new `Prototype`. + pub fn new(name: impl Into, return_type: impl Into) -> Self + { + Self { + name: name.into(), + return_type: return_type.into(), + } + } + + /// Returns the command prototype name. + #[must_use] + pub fn name(&self) -> &str + { + &self.name + } + + /// Returns the command prototype return type. + #[must_use] + pub fn return_type(&self) -> &str + { + &self.return_type + } +} + +impl FromElements for Prototype +{ + type Error = PrototypeError; + + fn from_elements( + elements: &crate::xml::element::Elements, + ) -> Result + { + let name = elements + .get_first_tagged_element("name") + .ok_or(Self::Error::MissingName)? + .child_elements() + .get_first_text_element() + .cloned() + .unwrap_or_default(); + + let return_type = find_type(elements); + + Ok(Self { name, return_type }) + } +} + +/// [`Prototype`] error. +#[derive(Debug, thiserror::Error)] +pub enum PrototypeError +{ + /// No 'name' element was found. + #[error("No 'name' element was found")] + MissingName, +} + +/// A command parameter. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Parameter +{ + name: String, + ty: String, +} + +impl Parameter +{ + /// Returns a new `Parameter`. + pub fn new(name: impl Into, ty: impl Into) -> Self + { + Self { + name: name.into(), + ty: ty.into(), + } + } + + /// Returns the name of the command parameter. + #[must_use] + pub fn name(&self) -> &str + { + &self.name + } + + /// Returns the type of the command parameter. + #[must_use] + pub fn get_type(&self) -> &str + { + &self.ty + } +} + +impl FromElements for Parameter +{ + type Error = ParameterError; + + fn from_elements(elements: &Elements) -> Result + { + let name = elements + .get_first_tagged_element("name") + .ok_or(Self::Error::MissingName)? + .child_elements() + .get_first_text_element() + .cloned() + .unwrap_or_default(); + + let ty = find_type(elements); + + Ok(Self { name, ty }) + } +} + +/// [`Parameter`] error. +#[derive(Debug, thiserror::Error)] +pub enum ParameterError +{ + /// No 'name' element was found. + #[error("No 'name' element was found")] + MissingName, +} + +fn find_type(elements: &Elements) -> String +{ + let text_type_parts = elements + .get_all_text_elements() + .into_iter() + .map(|text_type_part| text_type_part.trim()) + .filter(|text_type_part| !text_type_part.is_empty()) + .collect::>(); + + let opt_ptype_text = get_ptype_text(elements); + + opt_ptype_text.map_or_else( + || join_space_strs(text_type_parts.iter()), + |ptype_text| { + let Some(first_part) = text_type_parts.first() else { + return ptype_text.clone(); + }; + + let before = if *first_part == "const" { "const " } else { "" }; + + let after_start_index = usize::from(*first_part == "const"); + + format!( + "{before}{ptype_text} {}", + text_type_parts + .get(after_start_index..) + .map(|parts| join_space_strs(parts.iter())) + .unwrap_or_default() + ) + }, + ) +} + +fn get_ptype_text(elements: &Elements) -> Option<&String> +{ + let ptype_element = elements.get_first_tagged_element("ptype")?; + + ptype_element.child_elements().get_first_text_element() +} + +fn join_space_strs(strings: Strings) -> String +where + Strings: Iterator, + StrItem: ToString, +{ + strings + .into_iter() + .map(|string| string.to_string()) + .collect::>() + .join(" ") +} -- cgit v1.2.3-18-g5258