diff --git a/Cargo.toml b/Cargo.toml index 9bb5247..fc70f44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] members = [ - "vk-parse", - "ci", + "khronos-registry-parse", + "ci" ] diff --git a/README.md b/README.md index f1cc447..d973696 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ `vk-parse` is a Rust crate which parses the Vulkan API registry XML and converts it to a Rust representation. It can be used to simplify generation of Vulkan API bindings. -Crate itself resides in [vk-parse subdirectory](vk-parse), where you can also find more information about it. +Crate itself resides in [vk-parse subdirectory](khronos-registry-parse), where you can also find more information about it. diff --git a/ci/Cargo.toml b/ci/Cargo.toml index 087f6c4..561a1f9 100644 --- a/ci/Cargo.toml +++ b/ci/Cargo.toml @@ -10,6 +10,6 @@ minreq = { version = "^2", features = ["https"] } ron = "^0.4" serde = "^1.0.75" serde_derive = "^1.0.75" -vk-parse = { path = "../vk-parse", features = ["serialize", "vkxml-convert"] } +khronos-registry-parse = { path = "../khronos-registry-parse", features = ["serialize", "vkxml-convert", "vulkan", "opengl"] } vkxml = "^0.3" xml-rs = "^0.8" diff --git a/ci/tests/test.rs b/ci/tests/test.rs index 4e2f1bc..65d9f2a 100644 --- a/ci/tests/test.rs +++ b/ci/tests/test.rs @@ -1,14 +1,16 @@ -#![deny(warnings)] +extern crate khronos_registry_parse; -extern crate minreq; -extern crate ron; -extern crate serde; -extern crate vk_parse; -extern crate vkxml; -extern crate xml; +use khronos_registry_parse::gl; +use khronos_registry_parse::vk; +use std::fs::File; +use std::io::BufReader; +use std::path::{Path, PathBuf}; const URL_REPO: &str = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs"; -const URL_MAIN: &str = "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml"; +const URL_MAIN_VK: &str = + "https://raw.githubusercontent.com/KhronosGroup/Vulkan-Docs/main/xml/vk.xml"; +const URL_MAIN_GL: &str = + "https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/main/xml/gl.xml"; fn download(dst: &mut T, url: &str) { let resp = minreq::get(url) @@ -37,7 +39,7 @@ fn parsing_test(major: u32, minor: u32, patch: u32, url_suffix: &str) { download(&mut buf, &src); buf.set_position(0); - match vk_parse::parse_stream(buf.clone()) { + match vk::parse_stream(buf.clone()) { Ok((_reg, errors)) => { if !errors.is_empty() { panic!("{:?}", errors); @@ -46,7 +48,8 @@ fn parsing_test(major: u32, minor: u32, patch: u32, url_suffix: &str) { Err(fatal_error) => panic!("{:?}", fatal_error), } - match vk_parse::parse_stream_as_vkxml(buf) { + #[cfg(feature = "vkxml-convert")] + match vk::parse_stream_as_vkxml(buf) { Ok(_) => (), Err(fatal_error) => panic!("{:?}", fatal_error), } @@ -62,13 +65,30 @@ macro_rules! test_version { } #[test] -fn test_main() { +fn test_opengl_main() { use std::io::Cursor; let mut buf = Cursor::new(vec![0; 15]); - download(&mut buf, URL_MAIN); + download(&mut buf, URL_MAIN_GL); buf.set_position(0); - match vk_parse::parse_stream(buf.clone()) { + match gl::parse_stream(buf.clone()) { + Ok((_reg, errors)) => { + if !errors.is_empty() { + panic!("{:?}", errors); + } + } + Err(fatal_error) => panic!("{:?}", fatal_error), + } +} + +#[test] +fn test_vulkan_main() { + use std::io::Cursor; + let mut buf = Cursor::new(vec![0; 15]); + download(&mut buf, URL_MAIN_VK); + buf.set_position(0); + + match vk::parse_stream(buf.clone()) { Ok((_reg, errors)) => { if !errors.is_empty() { panic!("{:?}", errors); @@ -77,7 +97,7 @@ fn test_main() { Err(fatal_error) => panic!("{:?}", fatal_error), } - match vk_parse::parse_stream_as_vkxml(buf) { + match vk::parse_stream_as_vkxml(buf) { Ok(_) => (), Err(fatal_error) => panic!("{:?}", fatal_error), } diff --git a/vk-parse/Cargo.toml b/khronos-registry-parse/Cargo.toml similarity index 77% rename from vk-parse/Cargo.toml rename to khronos-registry-parse/Cargo.toml index 5062695..d54e0b6 100644 --- a/vk-parse/Cargo.toml +++ b/khronos-registry-parse/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "vk-parse" +name = "khronos-registry-parse" version = "0.7.0" authors = ["Martin Krošlák "] description = "Vulkan specification parser" @@ -12,13 +12,18 @@ categories = ["parser-implementations", "rendering::graphics-api"] [features] serialize = ["serde", "serde_derive"] -vkxml-convert = ["vkxml"] +vkxml-convert = ["vkxml", "vulkan"] +vulkan = [] +opengl = [] [dependencies] xml-rs = "^0.8" - vkxml = { optional = true, version = "^0.3" } serde = { optional = true, version = "^1.0.75" } serde_derive = { optional = true, version = "^1.0.75" } +[dev-dependencies] +minreq = { version = "^2", features = ["https"] } +ron = "^0.4" + [badges] diff --git a/vk-parse/README.md b/khronos-registry-parse/README.md similarity index 100% rename from vk-parse/README.md rename to khronos-registry-parse/README.md diff --git a/vk-parse/src/c.rs b/khronos-registry-parse/src/c.rs similarity index 100% rename from vk-parse/src/c.rs rename to khronos-registry-parse/src/c.rs diff --git a/khronos-registry-parse/src/gl/mod.rs b/khronos-registry-parse/src/gl/mod.rs new file mode 100644 index 0000000..27f9fd3 --- /dev/null +++ b/khronos-registry-parse/src/gl/mod.rs @@ -0,0 +1,7 @@ +mod parse; +mod types; + +pub use gl::parse::parse_file; +pub use gl::parse::parse_stream; +pub use gl::types::*; +pub use types::*; diff --git a/khronos-registry-parse/src/gl/parse.rs b/khronos-registry-parse/src/gl/parse.rs new file mode 100644 index 0000000..c9ca6fb --- /dev/null +++ b/khronos-registry-parse/src/gl/parse.rs @@ -0,0 +1,421 @@ +extern crate xml; + +use gl::types::*; +use std; +use std::io::Read; +use types::*; +use util::*; +use xml::reader::XmlEvent; + +pub const BOM: &'static [u8] = &[0xEF, 0xBB, 0xBF]; + +pub fn parse_file(path: &std::path::Path) -> Result<(Registry, Vec), FatalError> { + let file = std::io::BufReader::new(std::fs::File::open(path)?); + parse_stream(file) +} + +pub fn parse_stream(mut stream: T) -> Result<(Registry, Vec), FatalError> { + let mut buffer = vec![0; 0]; + stream.read_to_end(&mut buffer)?; + + let mut offset = 0; + for (i, _) in buffer.iter().enumerate() { + if buffer[i..].starts_with(BOM) { + offset = i + BOM.len(); + } + if i != 0 { + break; + } + } + + let parser = xml::reader::ParserConfig::new().create_reader(&buffer[offset..]); + parse_xml(parser.into_iter()) +} + +fn parse_xml(events: XmlEvents) -> Result<(Registry, Vec), FatalError> { + let mut ctx = ParseCtx { + events, + xpath: String::from(""), + errors: Vec::new(), + }; + + let mut result = Err(FatalError::MissingRegistryElement); + + { + let ctx = &mut ctx; + match_elements! {ctx, + "registry" => result = parse_registry(ctx) + } + } + + result.map(|r| (r, ctx.errors)) +} + +fn parse_registry(ctx: &mut ParseCtx) -> Result { + let mut registry = Registry(Vec::new()); + + match_elements! {ctx, attributes, + "comment" => registry.0.push(RegistryChild::Comment(parse_text_element(ctx))), + "enums" => { + let mut namespace = None; + let mut group = None; + let mut enum_type = None; + let mut start = None; + let mut end = None; + let mut vendor = None; + let mut comment = None; + let mut children = Vec::new(); + + match_attributes!{ctx, a in attributes, + "namespace" => namespace = Some(a.value), + "group" => group = Some(a.value), + "type" => enum_type = Some(a.value), + "start" => start = Some(a.value), + "end" => end = Some(a.value), + "vendor" => vendor = Some(a.value), + "comment" => comment = Some(a.value) + } + match_elements!{ctx, attributes, + "enum" => if let Some(v) = parse_enum(ctx, attributes) { + children.push(EnumsChild::Enum(v)); + }, + "unused" => { + consume_current_element(ctx); + }, + "comment" => children.push(EnumsChild::Comment(parse_text_element(ctx))) + } + registry.0.push(RegistryChild::Enums( + Enums{namespace, group, enum_type,start,end,vendor,comment, children })); + + }, + "types" => { + let mut children = Vec::new(); + match_elements!{ctx, attributes, + "type" => children.push(parse_type(ctx, attributes)) + } + registry.0.push(RegistryChild::Types(Types { children })); + }, + "commands" => { + let mut namespace = None; + let mut children = Vec::new(); + match_attributes!{ctx, a in attributes, + "namespace" => namespace = Some(a.value) + } + match_elements!{ctx, attributes, + "command" => if let Some(v) = parse_command(ctx, attributes) { + children.push(v); + } + } + registry.0.push(RegistryChild::Commands(Commands{namespace, children})); + }, + "extensions" => registry.0.push(parse_extensions(ctx, attributes)), + "feature" => { + let mut children = Vec::new(); + let mut api = None; + let mut name = None; + let mut number = None; + match_attributes!{ctx, a in attributes, + "api"=> api = Some(a.value), + "name" => name = Some(a.value), + "number" => number = Some(a.value) + } + match_elements!{ctx, attributes, + "require" => if let Some(ext) = parse_extension_items(ctx, ExtensionType::Required, attributes) { + children.push(ext); + }, + "remove" => if let Some(ext) = parse_extension_items(ctx, ExtensionType::Removed, attributes) { + children.push(ext); + } + } + registry.0.push(RegistryChild::Features(Features{name, api, number, children})); + } + } + Ok(registry) +} + +fn parse_extension_items( + ctx: &mut ParseCtx, + ext_type: ExtensionType, + attributes: Vec, +) -> Option { + let mut required_children = Vec::new(); + let mut required_comment = None; + let mut profile = None; + let mut api = None; + + match_attributes! {ctx, a in attributes, + "comment" => required_comment = Some(a.value), + "profile" => profile = Some(a.value), + "api" => api = Some(a.value) + } + + match_elements! { ctx, attributes, + "enum" => if let Some(e) = parse_enum(ctx, attributes) { + required_children.push(InterfaceItem::Enum(e)); + }, + "type" => { + let mut name = None; + let mut comment = None; + match_attributes! {ctx, a in attributes, + "name" => name = Some(a.value), + "comment" => comment = Some(a.value) + } + unwrap_attribute!(ctx, type, name); + consume_current_element(ctx); + required_children.push(InterfaceItem::Type { name, comment }); + }, + "command" => { + let mut name = None; + let mut comment = None; + match_attributes! {ctx, a in attributes, + "name" => name = Some(a.value), + "comment" => comment = Some(a.value) + } + unwrap_attribute!(ctx, type, name); + consume_current_element(ctx); + required_children.push(InterfaceItem::Command { name, comment }); + } + } + match ext_type { + ExtensionType::Required => Some(ExtensionChild::Require { + items: required_children, + comment: required_comment, + api, + }), + ExtensionType::Removed => Some(ExtensionChild::Removed { + items: required_children, + comment: required_comment, + profile, + }), + } +} + +fn parse_type(ctx: &mut ParseCtx, attributes: Vec) -> Type { + let mut name = None; + let mut type_name = None; + let mut requires = None; + let mut comment = None; + let mut code: String = String::new(); + let mut api_entry = false; + match_attributes! {ctx, a in attributes, + "requires" => requires = Some(a.value), + "comment" => comment = Some(a.value), + "name" => name = Some(a.value) + } + + match_elements_combine_text! {ctx, code, + "name" => { + type_name = Some(parse_text_element(ctx)); + }, + "apientry" => { + api_entry = true; + consume_current_element(ctx) //skip + } + } + + Type { + api_entry, + requires, + type_name, + name, + comment, + code, + } +} + +fn parse_extensions( + ctx: &mut ParseCtx, + _attributes: Vec, +) -> RegistryChild { + let mut children = Vec::new(); + match_elements! {ctx, attributes, + "extension" => if let Some(v) = parse_extension(ctx, attributes) { + children.push(v); + } + } + RegistryChild::Extensions(Extensions { children }) +} + +fn parse_extension( + ctx: &mut ParseCtx, + attributes: Vec, +) -> Option { + let mut name = None; + let mut supported = None; + let mut comment = None; + let mut children: Vec = Vec::new(); + + match_attributes! {ctx, a in attributes, + "name" => name = Some(a.value), + "supported" => supported = Some(a.value), + "comment" => comment = Some(a.value) + } + + match_elements! { ctx, attributes, + "require" => if let Some(ext) = parse_extension_items(ctx, ExtensionType::Required, attributes) { + children.push(ext); + } + } + + Some(Extension { + name, + supported, + comment, + children, + }) +} + +fn parse_command( + ctx: &mut ParseCtx, + _attributes: Vec, +) -> Option { + let mut code = String::new(); + let mut proto = None; + let mut vec_equiv = None; + let mut params = Vec::new(); + let mut aliases = Vec::new(); + let mut glx = None; + + match_elements! {ctx, attributes, + "proto" => { + let mut group = None; + let mut class = None; + match_attributes! {ctx, a in attributes, + "group" => group = Some(a.value), + "class" => class = Some(a.value) + } + if let Some(definition) = parse_name_with_type(ctx, &mut code) { + proto = Some(CommandProto { + group, + class, + definition + }); + } + code.push('('); + }, + "param" => { + let mut group = None; + let mut class = None; + let mut len = None; + + match_attributes!{ctx, a in attributes, + "group" => group = Some(a.value), + "class" => class = Some(a.value), + "len" => len = Some(a.value) + } + if params.len() > 0 { + code.push_str(", "); + } + + if let Some(definition) = parse_name_with_type(ctx, &mut code) { + params.push(CommandParam { + group, + class, + len, + definition + }); + } + }, + "alias" => { + match_attributes!{ctx, a in attributes, + "name" => aliases.push(a.value) + } + consume_current_element(ctx); + }, + "glx" => { + let mut glx_type = None; + let mut opcode = None; + let mut name = None; + let mut comment = None; + match_attributes!{ctx, a in attributes, + "type" => glx_type = Some(a.value), + "opcode" => opcode = Some(a.value), + "name" => name = Some(a.value), + "comment" => comment = Some(a.value) + } + glx = Some(Glx { + glx_type, + opcode, + name, + comment + }); + consume_current_element(ctx); + }, + "vecequiv" => { + match_attributes!{ctx, a in attributes, + "name" => vec_equiv = Some(a.value) + } + consume_current_element(ctx); + } + } + let proto = if let Some(v) = proto { + v + } else { + ctx.errors.push(Error::MissingElement { + xpath: ctx.xpath.clone(), + name: String::from("proto"), + }); + return None; + }; + Some(Command { + proto, + params, + code, + vec_equiv, + glx, + aliases, + }) +} + +fn parse_name_with_type( + ctx: &mut ParseCtx, + buffer: &mut String, +) -> Option { + let mut name = None; + let mut type_name = None; + + let mut arg_buffer: String = String::new(); + match_elements_combine_text! {ctx, arg_buffer, + "ptype" => { + let text = parse_text_element(ctx); + arg_buffer.push_str(&text); + type_name = Some(text); + }, + "name" => { + let text = parse_text_element(ctx); + arg_buffer.push_str(&text); + name = Some(text); + } + } + buffer.push_str(arg_buffer.as_str()); + + let name = if let Some(v) = name { + v + } else { + ctx.errors.push(Error::MissingElement { + xpath: ctx.xpath.clone(), + name: String::from("name"), + }); + return None; + }; + + Some(NameWithType { name, type_name, code: arg_buffer.to_string() }) +} + +fn parse_enum(ctx: &mut ParseCtx, attributes: Vec) -> Option { + let mut name = None; + let mut value = None; + let mut group = None; + match_attributes! {ctx, a in attributes, + "name" => name = Some(a.value), + "value" => value = Some(a.value), + "group" => group = Some(a.value), + "alias" => {}, + "type" => {}, + "api" => {}, + "comment" => {} + } + consume_current_element(ctx); + unwrap_attribute!(ctx, enum, name); + Some(Enum { name, value, group }) +} diff --git a/khronos-registry-parse/src/gl/types.rs b/khronos-registry-parse/src/gl/types.rs new file mode 100644 index 0000000..4f9a630 --- /dev/null +++ b/khronos-registry-parse/src/gl/types.rs @@ -0,0 +1,474 @@ +#![allow(non_snake_case)] + +/// Rust structure representing the opengl registry. +/// +/// The registry contains all the information contained in a certain version +/// of the Vulkan specification, stored within a programmer-accessible format. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +pub struct Registry(pub Vec); + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct NameWithType { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub type_name: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub name: String, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub code: String +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct CommandProto { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub group: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub class: Option, + + /// The definition of this parameter. + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub definition: NameWithType, + +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct CommandParam { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub group: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub class: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub len: Option, + + /// The definition of this parameter. + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub definition: NameWithType, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct CommentedChildren { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub namespace: Option, + + pub children: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Glx { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub name: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub comment: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub glx_type: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub opcode: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Command { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub proto: CommandProto, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub params: Vec, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub code: String, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub vec_equiv: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub aliases: Vec, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub glx: Option, +} + +pub type Commands = CommentedChildren; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Extensions { + pub children: Vec, +} + +/// An interface item is a function or an enum which makes up a Vulkan interface. +/// +/// This structure is used by extensions to express dependencies or include functionality. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub enum InterfaceItem { + Enum(Enum), + Type { + name: String, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + comment: Option, + }, + Command { + name: String, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + comment: Option, + }, +} + +/// A part of an extension declaration. +/// +/// Extensions either include functionality from the spec, or remove some functionality. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub enum ExtensionChild { + /// Indicates the items which this extension requires to work. + Require { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + items: Vec, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + comment: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + api: Option, + }, + Removed { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + items: Vec, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + comment: Option, + + profile: Option, + }, +} + +pub enum ExtensionType { + Required, + Removed, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Extension { + pub name: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub supported: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub comment: Option, + + /// The items which make up this extension. + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub children: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Features { + pub name: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub api: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub number: Option, + + /// The items which make up this extension. + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub children: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Type { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub requires: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub comment: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub name: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub type_name: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub code: String, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub api_entry: bool, +} +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Types { + pub children: Vec, +} + +/// An element of the Vulkan registry. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub enum RegistryChild { + /// Comments are human-readable strings which contain registry meta-data. + Comment(String), + + /// Unlike OpenGL, Vulkan is a strongly-typed API. + Types(Types), + + /// Enum definitions. + Enums(Enums), + + /// Commands are the Vulkan API's name for functions. + Commands(Commands), + + /// Container for all published Vulkan specification extensions. + Extensions(Extensions), + + /// Container for all published Vulkan specification extensions. + Features(Features), +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Enums { + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub namespace: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub group: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub enum_type: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub start: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub end: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub vendor: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub comment: Option, + + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub children: Vec, +} + +/// An item of an enumeration type. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub struct Enum { + /// Name of this enum. + pub name: String, + + /// Human-readable description. + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub value: Option, + + /// Human-readable description. + #[cfg_attr( + feature = "serialize", + serde(default, skip_serializing_if = "is_default") + )] + pub group: Option, +} + +/// An item which forms an enum. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))] +#[non_exhaustive] +pub enum EnumsChild { + /// Actual named enum. + Enum(Enum), + // + // /// An unused range of enum values. + // Unused(Unused), + /// Human-readable comment. + Comment(String), +} + +#[cfg(feature = "serialize")] +fn is_default(v: &T) -> bool { + v.eq(&T::default()) +} diff --git a/vk-parse/src/lib.rs b/khronos-registry-parse/src/lib.rs similarity index 59% rename from vk-parse/src/lib.rs rename to khronos-registry-parse/src/lib.rs index 3e1aefe..d861707 100644 --- a/vk-parse/src/lib.rs +++ b/khronos-registry-parse/src/lib.rs @@ -12,20 +12,14 @@ extern crate serde_derive; #[cfg(feature = "serialize")] extern crate serde; -#[cfg(feature = "vkxml-convert")] -extern crate vkxml; +pub use types::*; #[macro_use] -mod parse; +mod util; mod c; -#[cfg(feature = "vkxml-convert")] -mod convert; mod types; -#[cfg(feature = "vkxml-convert")] -pub use convert::parse_file_as_vkxml; -#[cfg(feature = "vkxml-convert")] -pub use convert::parse_stream_as_vkxml; -pub use parse::parse_file; -pub use parse::parse_stream; -pub use types::*; +// #[cfg(feature = "opengl")] +pub mod gl; +#[cfg(feature = "vulkan")] +pub mod vk; diff --git a/khronos-registry-parse/src/types.rs b/khronos-registry-parse/src/types.rs new file mode 100644 index 0000000..4c20f7d --- /dev/null +++ b/khronos-registry-parse/src/types.rs @@ -0,0 +1,56 @@ +/// Errors from which parser cannot recover. +#[derive(Debug)] +#[non_exhaustive] +pub enum FatalError { + MissingRegistryElement, + IoError(std::io::Error), +} + +impl From for FatalError { + fn from(v: std::io::Error) -> FatalError { + FatalError::IoError(v) + } +} + +/// Errors from which parser can recover. How much information will be missing +/// in the resulting Registry depends on the type of error and situation in +/// which it occurs. For example, unrecognized attribute will simply be skipped +/// without affecting anything around it, while unrecognized element will have +/// all of its contents skipped. +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Error { + UnexpectedElement { + xpath: String, + name: String, + }, + UnexpectedAttribute { + xpath: String, + name: String, + }, // "Missing attribute '{}' on element '{}'." + UnexpectedAttributeValue { + xpath: String, + name: String, + value: String, + }, + MissingElement { + xpath: String, + name: String, + }, + MissingAttribute { + xpath: String, + name: String, + }, + SchemaViolation { + xpath: String, + desc: String, + }, + ParseIntError { + xpath: String, + text: String, + error: std::num::ParseIntError, + }, + Internal { + desc: &'static str, + }, +} diff --git a/khronos-registry-parse/src/util.rs b/khronos-registry-parse/src/util.rs new file mode 100644 index 0000000..6d06162 --- /dev/null +++ b/khronos-registry-parse/src/util.rs @@ -0,0 +1,277 @@ +use crate::types::Error; +use std::io::Read; +use std::str::FromStr; +use xml::reader::XmlEvent; + +pub type XmlEvents = xml::reader::Events; +pub type XmlAttribute = xml::attribute::OwnedAttribute; + +pub fn xpath_attribute(xpath: &str, attribute_name: &str) -> String { + let mut xpath = String::from(xpath); + xpath.push_str("[@"); + xpath.push_str(attribute_name); + xpath.push(']'); + xpath +} + +//-------------------------------------------------------------------------------------------------- +#[macro_export] +macro_rules! unwrap_attribute ( + ($ctx:expr, $element:ident, $attribute:ident) => { + let $attribute = match $attribute { + Some(val) => val, + None => { + $ctx.errors.push(Error::MissingAttribute { + xpath: $ctx.xpath.clone(), + name: String::from(stringify!($attribute)), + }); + return None; + } + }; + }; +); + +#[macro_export] +macro_rules! match_attributes { + ($ctx:expr, $a:ident in $attributes:expr, $($p:pat => $e:expr),+) => { + for $a in $attributes { + let n = $a.name.local_name.as_str(); + match n { + $( + $p => $e, + )+ + _ => $ctx.errors.push(Error::UnexpectedAttribute { + xpath: $ctx.xpath.clone(), + name: String::from(n), + }) + } + } + }; +} + +#[macro_export] +macro_rules! match_elements { + ($ctx:expr, $($p:pat => $e:expr),+) => { + while let Some(Ok(e)) = $ctx.events.next() { + match e { + XmlEvent::StartElement { name, .. } => { + let name = name.local_name.as_str(); + $ctx.push_element(name); + match name { + $( + $p => $e, + )+ + _ => { + $ctx.errors.push(Error::UnexpectedElement { + xpath: $ctx.xpath.clone(), + name: String::from(name), + }); + consume_current_element($ctx); + } + } + } + XmlEvent::EndElement { .. } => { + $ctx.pop_element(); + break; + } + _ => {} + } + } + }; + + ( $ctx:expr, $attributes:ident, $($p:pat => $e:expr),+) => { + while let Some(Ok(e)) = $ctx.events.next() { + match e { + XmlEvent::StartElement { name, $attributes, .. } => { + let name = name.local_name.as_str(); + $ctx.push_element(name); + match name { + $( + $p => $e, + )+ + _ => { + $ctx.errors.push(Error::UnexpectedElement { + xpath: $ctx.xpath.clone(), + name: String::from(name), + }); + consume_current_element($ctx); + } + } + } + XmlEvent::EndElement { .. } => { + $ctx.pop_element(); + break; + } + _ => {} + } + } + }; +} + +#[macro_export] +macro_rules! match_elements_combine_text { + ( $ctx:expr, $buffer:ident, $($p:pat => $e:expr),+) => { + while let Some(Ok(e)) = $ctx.events.next() { + match e { + XmlEvent::Characters(text) => $buffer.push_str(&text), + XmlEvent::Whitespace(text) => $buffer.push_str(&text), + XmlEvent::StartElement { name, .. } => { + $buffer.push(' '); + let name = name.local_name.as_str(); + $ctx.push_element(name); + match name { + $( + $p => $e, + )+ + _ => { + $ctx.errors.push(Error::UnexpectedElement { + xpath: $ctx.xpath.clone(), + name: String::from(name), + }); + consume_current_element($ctx); + } + } + } + XmlEvent::EndElement { .. } => { + $buffer.push(' '); + $ctx.pop_element(); + break; + }, + _ => {} + } + } + }; + + ( $ctx:expr, $attributes:ident, $buffer:ident, $($p:pat => $e:expr),+) => { + while let Some(Ok(e)) = $ctx.events.next() { + match e { + XmlEvent::Characters(text) => $buffer.push_str(&text), + XmlEvent::Whitespace(text) => $buffer.push_str(&text), + XmlEvent::StartElement { name, $attributes, .. } => { + let name = name.local_name.as_str(); + $ctx.push_element(name); + match name { + $( + $p => $e, + )+ + _ => { + $ctx.errors.push(Error::UnexpectedElement { + xpath: $ctx.xpath.clone(), + name: String::from(name), + }); + consume_current_element($ctx); + } + } + } + XmlEvent::EndElement { .. } => { + $ctx.pop_element(); + break; + } + _ => {} + } + } + }; +} + +//-------------------------------------------------------------------------------------------------- +pub struct ParseCtx { + pub events: XmlEvents, + pub xpath: String, + pub errors: Vec, +} + +impl ParseCtx { + pub fn push_element(&mut self, name: &str) { + self.xpath.push('/'); + self.xpath.push_str(name); + } + + pub fn pop_element(&mut self) { + if let Some(separator_pos) = self.xpath.rfind('/') { + self.xpath.truncate(separator_pos); + } else { + self.errors.push(Error::Internal { + desc: "ParseCtx push_element/pop_element mismatch.", + }); + } + } +} + +pub fn parse_integer(ctx: &mut ParseCtx, text: &str) -> Option { + let parse_res = if text.starts_with("0x") { + i64::from_str_radix(text.split_at(2).1, 16) + } else { + i64::from_str_radix(text, 10) + }; + + if let Ok(v) = parse_res { + Some(v) + } else { + ctx.errors.push(Error::SchemaViolation { + xpath: ctx.xpath.clone(), + desc: format!("Value '{}' is not valid base 10 or 16 integer.", text), + }); + None + } +} + +pub fn parse_int_attribute, R: Read>( + ctx: &mut ParseCtx, + text: String, + attribute_name: &str, +) -> Option { + match I::from_str(&text) { + Ok(v) => Some(v), + Err(e) => { + ctx.errors.push(Error::ParseIntError { + xpath: xpath_attribute(&ctx.xpath, attribute_name), + text: text, + error: e, + }); + None + } + } +} + +pub fn consume_current_element(ctx: &mut ParseCtx) { + let mut depth = 1; + while let Some(Ok(e)) = ctx.events.next() { + match e { + XmlEvent::StartElement { name, .. } => { + ctx.push_element(name.local_name.as_str()); + depth += 1; + } + XmlEvent::EndElement { .. } => { + depth -= 1; + ctx.pop_element(); + if depth == 0 { + break; + } + } + _ => (), + } + } +} + +pub fn parse_text_element(ctx: &mut ParseCtx) -> String { + let mut result = String::new(); + let mut depth = 1; + while let Some(Ok(e)) = ctx.events.next() { + match e { + XmlEvent::StartElement { name, .. } => { + ctx.push_element(name.local_name.as_str()); + depth += 1; + } + XmlEvent::Characters(text) => result.push_str(&text), + XmlEvent::EndElement { .. } => { + depth -= 1; + ctx.pop_element(); + if depth == 0 { + break; + } + } + _ => (), + } + } + result +} diff --git a/vk-parse/src/convert.rs b/khronos-registry-parse/src/vk/convert.rs similarity index 99% rename from vk-parse/src/convert.rs rename to khronos-registry-parse/src/vk/convert.rs index d0f7c82..c1716bc 100644 --- a/vk-parse/src/convert.rs +++ b/khronos-registry-parse/src/vk/convert.rs @@ -1,9 +1,10 @@ extern crate vkxml; use c; -use parse::*; use std; use types::*; +use vk::parse::*; +use vk::types::*; //-------------------------------------------------------------------------------------------------- fn new_field() -> vkxml::Field { diff --git a/khronos-registry-parse/src/vk/mod.rs b/khronos-registry-parse/src/vk/mod.rs new file mode 100644 index 0000000..554a3e4 --- /dev/null +++ b/khronos-registry-parse/src/vk/mod.rs @@ -0,0 +1,16 @@ +#[cfg(feature = "vkxml-convert")] +mod convert; +mod parse; +mod types; + +#[cfg(feature = "vkxml-convert")] +extern crate vkxml; +#[cfg(feature = "vkxml-convert")] +pub use vk::convert::parse_file_as_vkxml; +#[cfg(feature = "vkxml-convert")] +pub use vk::convert::parse_stream_as_vkxml; + +pub use types::*; +pub use vk::parse::parse_file; +pub use vk::parse::parse_stream; +pub use vk::types::*; diff --git a/vk-parse/src/parse.rs b/khronos-registry-parse/src/vk/parse.rs similarity index 82% rename from vk-parse/src/parse.rs rename to khronos-registry-parse/src/vk/parse.rs index be0601b..45daf32 100644 --- a/vk-parse/src/parse.rs +++ b/khronos-registry-parse/src/vk/parse.rs @@ -2,200 +2,10 @@ extern crate xml; use std; use std::io::Read; -use std::str::FromStr; -use xml::reader::XmlEvent; - use types::*; - -type XmlEvents = xml::reader::Events; -type XmlAttribute = xml::attribute::OwnedAttribute; - -//-------------------------------------------------------------------------------------------------- -struct ParseCtx { - events: XmlEvents, - xpath: String, - errors: Vec, -} - -impl ParseCtx { - fn push_element(&mut self, name: &str) { - self.xpath.push('/'); - self.xpath.push_str(name); - } - - fn pop_element(&mut self) { - if let Some(separator_pos) = self.xpath.rfind('/') { - self.xpath.truncate(separator_pos); - } else { - self.errors.push(Error::Internal { - desc: "ParseCtx push_element/pop_element mismatch.", - }); - } - } -} - -fn xpath_attribute(xpath: &str, attribute_name: &str) -> String { - let mut xpath = String::from(xpath); - xpath.push_str("[@"); - xpath.push_str(attribute_name); - xpath.push(']'); - xpath -} - -//-------------------------------------------------------------------------------------------------- -macro_rules! unwrap_attribute ( - ($ctx:expr, $element:ident, $attribute:ident) => { - let $attribute = match $attribute { - Some(val) => val, - None => { - $ctx.errors.push(Error::MissingAttribute { - xpath: $ctx.xpath.clone(), - name: String::from(stringify!($attribute)), - }); - return None; - } - }; - }; -); - -macro_rules! match_attributes { - ($ctx:expr, $a:ident in $attributes:expr, $($p:pat => $e:expr),+) => { - for $a in $attributes { - let n = $a.name.local_name.as_str(); - match n { - $( - $p => $e, - )+ - _ => $ctx.errors.push(Error::UnexpectedAttribute { - xpath: $ctx.xpath.clone(), - name: String::from(n), - }) - } - } - }; -} - -macro_rules! match_elements { - ($ctx:expr, $($p:pat => $e:expr),+) => { - while let Some(Ok(e)) = $ctx.events.next() { - match e { - XmlEvent::StartElement { name, .. } => { - let name = name.local_name.as_str(); - $ctx.push_element(name); - match name { - $( - $p => $e, - )+ - _ => { - $ctx.errors.push(Error::UnexpectedElement { - xpath: $ctx.xpath.clone(), - name: String::from(name), - }); - consume_current_element($ctx); - } - } - } - XmlEvent::EndElement { .. } => { - $ctx.pop_element(); - break; - } - _ => {} - } - } - }; - - ( $ctx:expr, $attributes:ident, $($p:pat => $e:expr),+) => { - while let Some(Ok(e)) = $ctx.events.next() { - match e { - XmlEvent::StartElement { name, $attributes, .. } => { - let name = name.local_name.as_str(); - $ctx.push_element(name); - match name { - $( - $p => $e, - )+ - _ => { - $ctx.errors.push(Error::UnexpectedElement { - xpath: $ctx.xpath.clone(), - name: String::from(name), - }); - consume_current_element($ctx); - } - } - } - XmlEvent::EndElement { .. } => { - $ctx.pop_element(); - break; - } - _ => {} - } - } - }; -} - -macro_rules! match_elements_combine_text { - ( $ctx:expr, $buffer:ident, $($p:pat => $e:expr),+) => { - while let Some(Ok(e)) = $ctx.events.next() { - match e { - XmlEvent::Characters(text) => $buffer.push_str(&text), - XmlEvent::Whitespace(text) => $buffer.push_str(&text), - XmlEvent::StartElement { name, .. } => { - $buffer.push(' '); - let name = name.local_name.as_str(); - $ctx.push_element(name); - match name { - $( - $p => $e, - )+ - _ => { - $ctx.errors.push(Error::UnexpectedElement { - xpath: $ctx.xpath.clone(), - name: String::from(name), - }); - consume_current_element($ctx); - } - } - } - XmlEvent::EndElement { .. } => { - $buffer.push(' '); - $ctx.pop_element(); - break; - }, - _ => {} - } - } - }; - - ( $ctx:expr, $attributes:ident, $buffer:ident, $($p:pat => $e:expr),+) => { - while let Some(Ok(e)) = $ctx.events.next() { - match e { - XmlEvent::Characters(text) => $buffer.push_str(&text), - XmlEvent::Whitespace(text) => $buffer.push_str(&text), - XmlEvent::StartElement { name, $attributes, .. } => { - let name = name.local_name.as_str(); - $ctx.push_element(name); - match name { - $( - $p => $e, - )+ - _ => { - $ctx.errors.push(Error::UnexpectedElement { - xpath: $ctx.xpath.clone(), - name: String::from(name), - }); - consume_current_element($ctx); - } - } - } - XmlEvent::EndElement { .. } => { - $ctx.pop_element(); - break; - } - _ => {} - } - } - }; -} +use util::*; +use vk::types::*; +use xml::reader::XmlEvent; //-------------------------------------------------------------------------------------------------- /// Parses the Vulkan XML file into a Rust object. @@ -1498,82 +1308,3 @@ fn parse_enable(ctx: &mut ParseCtx, attributes: Vec) - unimplemented!(); } } - -fn parse_integer(ctx: &mut ParseCtx, text: &str) -> Option { - let parse_res = if text.starts_with("0x") { - i64::from_str_radix(text.split_at(2).1, 16) - } else { - i64::from_str_radix(text, 10) - }; - - if let Ok(v) = parse_res { - Some(v) - } else { - ctx.errors.push(Error::SchemaViolation { - xpath: ctx.xpath.clone(), - desc: format!("Value '{}' is not valid base 10 or 16 integer.", text), - }); - None - } -} - -fn parse_int_attribute, R: Read>( - ctx: &mut ParseCtx, - text: String, - attribute_name: &str, -) -> Option { - match I::from_str(&text) { - Ok(v) => Some(v), - Err(e) => { - ctx.errors.push(Error::ParseIntError { - xpath: xpath_attribute(&ctx.xpath, attribute_name), - text: text, - error: e, - }); - None - } - } -} - -fn consume_current_element(ctx: &mut ParseCtx) { - let mut depth = 1; - while let Some(Ok(e)) = ctx.events.next() { - match e { - XmlEvent::StartElement { name, .. } => { - ctx.push_element(name.local_name.as_str()); - depth += 1; - } - XmlEvent::EndElement { .. } => { - depth -= 1; - ctx.pop_element(); - if depth == 0 { - break; - } - } - _ => (), - } - } -} - -fn parse_text_element(ctx: &mut ParseCtx) -> String { - let mut result = String::new(); - let mut depth = 1; - while let Some(Ok(e)) = ctx.events.next() { - match e { - XmlEvent::StartElement { name, .. } => { - ctx.push_element(name.local_name.as_str()); - depth += 1; - } - XmlEvent::Characters(text) => result.push_str(&text), - XmlEvent::EndElement { .. } => { - depth -= 1; - ctx.pop_element(); - if depth == 0 { - break; - } - } - _ => (), - } - } - result -} diff --git a/vk-parse/src/types.rs b/khronos-registry-parse/src/vk/types.rs similarity index 95% rename from vk-parse/src/types.rs rename to khronos-registry-parse/src/vk/types.rs index 19ae3b7..88e29e6 100644 --- a/vk-parse/src/types.rs +++ b/khronos-registry-parse/src/vk/types.rs @@ -1,62 +1,5 @@ #![allow(non_snake_case)] -/// Errors from which parser cannot recover. -#[derive(Debug)] -#[non_exhaustive] -pub enum FatalError { - MissingRegistryElement, - IoError(std::io::Error), -} - -impl From for FatalError { - fn from(v: std::io::Error) -> FatalError { - FatalError::IoError(v) - } -} - -/// Errors from which parser can recover. How much information will be missing -/// in the resulting Registry depends on the type of error and situation in -/// which it occurs. For example, unrecognized attribute will simply be skipped -/// without affecting anything around it, while unrecognized element will have -/// all of its contents skipped. -#[derive(Debug, PartialEq, Eq)] -#[non_exhaustive] -pub enum Error { - UnexpectedElement { - xpath: String, - name: String, - }, - UnexpectedAttribute { - xpath: String, - name: String, - }, // "Missing attribute '{}' on element '{}'." - UnexpectedAttributeValue { - xpath: String, - name: String, - value: String, - }, - MissingElement { - xpath: String, - name: String, - }, - MissingAttribute { - xpath: String, - name: String, - }, - SchemaViolation { - xpath: String, - desc: String, - }, - ParseIntError { - xpath: String, - text: String, - error: std::num::ParseIntError, - }, - Internal { - desc: &'static str, - }, -} - /// Rust structure representing the Vulkan registry. /// /// The registry contains all the information contained in a certain version