aboutsummaryrefslogtreecommitdiff
path: root/src/command.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/command.rs')
-rw-r--r--src/command.rs263
1 files changed, 263 insertions, 0 deletions
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<Parameter>,
+}
+
+impl Command
+{
+ /// Returns a new `Command`.
+ pub fn new(
+ prototype: Prototype,
+ parameters: impl IntoIterator<Item = Parameter>,
+ ) -> 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<Self, Self::Error>
+ {
+ 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::<Result<Vec<_>, _>>()?;
+
+ 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<String>, return_type: impl Into<String>) -> 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<Self, Self::Error>
+ {
+ 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<String>, ty: impl Into<String>) -> 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<Self, Self::Error>
+ {
+ 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::<Vec<_>>();
+
+ 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, StrItem>(strings: Strings) -> String
+where
+ Strings: Iterator<Item = StrItem>,
+ StrItem: ToString,
+{
+ strings
+ .into_iter()
+ .map(|string| string.to_string())
+ .collect::<Vec<_>>()
+ .join(" ")
+}