// Copyright 2015 Brendan Zabarauskas and the gl-rs developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
extern crate khronos_api;
use std::borrow::Cow;
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet};
use std::io;
use xml::attribute::OwnedAttribute;
use xml::reader::XmlEvent;
use xml::EventReader
as XmlEventReader;
use registry::{Binding, Cmd,
Enum , GlxOpcode, Group, Registry};
use {Api, Fallbacks, Profile};
pub fn from_xml<R: io::Read>(
src: R,
filter: &Filter,
require_feature: bool,
) -> Registry {
XmlEventReader::new(src)
.into_iter()
.map(Result::unwrap)
.filter_map(ParseEvent::from_xml)
.parse(filter, require_feature)
}
#[ derive(Debug, PartialEq, Eq)]
struct Attribute {
key: String,
value: String,
}
impl Attribute {
fn new<Key, Value>(key: Key, value: Value) -> Attribute
where
Key: ToString,
Value: ToString,
{
Attribute {
key: key.to_string(),
value: value.to_string(),
}
}
}
impl From<OwnedAttribute>
for Attribute {
fn from(attribute: OwnedAttribute) -> Attribute {
Attribute::new(attribute.name.local_name, attribute.value)
}
}
#[ derive(Debug, PartialEq, Eq)]
enum ParseEvent {
Start(String, Vec<Attribute>),
End(String),
Text(String),
}
impl ParseEvent {
fn from_xml(event: XmlEvent) -> Option<ParseEvent> {
match event {
XmlEvent::StartDocument { .. } => None,
XmlEvent::EndDocument => None,
XmlEvent::StartElement {
name, attributes, ..
} => {
let attributes = attributes.into_iter().map(Attribute::from).collect();
Some(ParseEvent::Start(name.local_name, attributes))
},
XmlEvent::EndElement { name } => Some(ParseEvent::End(name.local_name)),
XmlEvent::Characters(chars) => Some(ParseEvent::Text(chars)),
XmlEvent::ProcessingInstruction { .. } => None,
XmlEvent::CData(_) => None,
XmlEvent::Comment(_) => None,
XmlEvent::Whitespace(_) => None,
}
}
}
fn api_from_str(src: &str) -> Result<Option<Api>, ()> {
match src {
"gl" => Ok(Some(Api::Gl)),
"glx" => Ok(Some(Api::Glx)),
"wgl" => Ok(Some(Api::Wgl)),
"egl" => Ok(Some(Api::Egl)),
"glcore" => Ok(Some(Api::GlCore)),
"gles1" => Ok(Some(Api::Gles1)),
"gles2" => Ok(Some(Api::Gles2)),
"glsc2" => Ok(Some(Api::Glsc2)),
"disabled" => Ok(None),
_ => Err(()),
}
}
fn profile_from_str(src: &str) -> Result<Profile, ()> {
match src {
"core" => Ok(Profile::Core),
"compatibility" => Ok(Profile::Compatibility),
_ => Err(()),
}
}
fn underscore_numeric_prefix(src: &str) -> String {
match src.chars().next() {
Some(c)
if c.is_numeric() => format!(
"_{}" , src),
Some(_) | None => src.to_string(),
}
}
fn underscore_keyword(ident: String) -> String {
match ident.as_ref() {
"in" =>
"in_" .to_string(),
"ref" =>
"ref_" .to_string(),
"type" =>
"type_" .to_string(),
_ => ident,
}
}
fn trim_str<
'a>(s: &' a str, trim: &str) -> &
'a str {
if s.starts_with(trim) {
&s[trim.len()..]
}
else {
s
}
}
fn trim_enum_prefix(ident: &str, api: Api) -> String {
let ident =
match api {
Api::Gl | Api::GlCore | Api::Gles1 | Api::Gles2 | Api::Glsc2 => trim_str(ident,
"GL_" ),
Api::Glx => trim_str(ident,
"GLX_" ),
Api::Wgl => trim_str(ident,
"WGL_" ),
Api::Egl => trim_str(ident,
"EGL_" ),
};
underscore_numeric_prefix(ident)
}
fn make_enum(ident: String, ty: Option<String>, value: String, alias: Option<String>) ->
Enum {
let (ty, value, cast) = {
if value.starts_with(
"((" ) && value.ends_with(
")" ) {
// Some enums have a value of the form `'((' type ')' expr ')'`.
// nothing to see here....
// just brute forcing some paren matching... (ノ ◕ ◡ ◕)ノ *:・゚✧
let working = &value[
2 ..value.len() -
1 ];
if let Some((i, _)) = working.match_indices(
")" ).next() {
let ty = working[..i].to_string();
let value = working[i +
1 ..].to_string();
(Cow::Owned(ty), value,
true )
}
else {
panic!(
"Unexpected value format: {}" , value)
}
}
else {
let ty =
match ty {
Some(
ref ty)
if ty ==
"u" =>
"GLuint" ,
Some(
ref ty)
if ty ==
"ull" =>
"GLuint64" ,
Some(ty) => panic!(
"Unhandled enum type: {}" , ty),
None
if value.starts_with(
"\" ") => " &
'static str",
None
if ident ==
"TRUE" || ident ==
"FALSE" =>
"GLboolean" ,
None =>
"GLenum" ,
};
(Cow::Borrowed(ty), value,
false )
}
};
Enum {
ident: ident,
value: value,
cast: cast,
alias: alias,
ty: ty,
}
}
fn make_egl_enum(ident: String, ty: Option<String>, value: String, alias: Option<String>) ->
Enum {
let (ty, value, cast) = {
if value.starts_with(
"EGL_CAST(" ) && value.ends_with(
")" ) {
// Handling "SpecialNumbers" in the egl.xml file
// The values for these enums has the form `'EGL_CAST(' type ',' expr ')'`.
let working = &value[
9 ..value.len() -
1 ];
if let Some((i, _)) = working.match_indices(
"," ).next() {
let ty = working[..i].to_string();
let value = working[i +
1 ..].to_string();
(Cow::Owned(ty), value,
true )
}
else {
panic!(
"Unexpected value format: {}" , value)
}
}
else {
match value.chars().next() {
Some(
'-' ) | Some(
'0' ..=
'9' ) => (),
_ => panic!(
"Unexpected value format: {}" , value),
}
let ty =
match ty {
Some(
ref ty)
if ty ==
"ull" =>
"EGLuint64KHR" ,
Some(ty) => panic!(
"Unhandled enum type: {}" , ty),
None
if value.starts_with(
'-' ) =>
"EGLint" ,
None
if ident ==
"TRUE" || ident ==
"FALSE" =>
"EGLBoolean" ,
None =>
"EGLenum" ,
};
(Cow::Borrowed(ty), value,
false )
}
};
Enum {
ident: ident,
value: value,
cast: cast,
alias: alias,
ty: ty,
}
}
fn trim_cmd_prefix(ident: &str, api: Api) -> &str {
match api {
Api::Gl | Api::GlCore | Api::Gles1 | Api::Gles2 | Api::Glsc2 => trim_str(ident,
"gl" ),
Api::Glx => trim_str(ident,
"glX" ),
Api::Wgl => trim_str(ident,
"wgl" ),
Api::Egl => trim_str(ident,
"egl" ),
}
}
fn merge_map(a: &
mut BTreeMap<String, Vec<String>>, b: BTreeMap<Strin
g, Vec<String>>) {
for (k, v) in b {
match a.entry(k) {
Entry::Occupied(mut ent) => {
ent.get_mut().extend(v);
},
Entry::Vacant(ent) => {
ent.insert(v);
},
}
}
}
#[ derive(Clone)]
struct Feature {
pub api: Api,
pub name: String,
pub number: String,
pub requires: Vec<Require>,
pub removes: Vec<Remove>,
}
#[ derive(Clone)]
struct Require {
/// A reference to the earlier types, by name
pub enums: Vec<String>,
/// A reference to the earlier types, by name
pub commands: Vec<String>,
}
#[ derive(Clone)]
struct Remove {
// always Core, for now
pub profile: Profile,
/// A reference to the earlier types, by name
pub enums: Vec<String>,
/// A reference to the earlier types, by name
pub commands: Vec<String>,
}
#[ derive(Clone)]
struct Extension {
pub name: String,
/// which apis this extension is defined for (see Feature.api)
pub supported: Vec<Api>,
pub requires: Vec<Require>,
}
pub struct Filter {
pub api: Api,
pub fallbacks: Fallbacks,
pub extensions: BTreeSet<String>,
pub profile: Profile,
pub version: String,
}
trait Parse: Sized + Iterator<Item = ParseEvent> {
fn parse(mut self , filter: &Filter, require_feature: bool) -> Registry {
self .consume_start_element("registry" );
let mut enums = Vec::new();
let mut cmds = Vec::new();
let mut features = Vec::new();
let mut extensions = Vec::new();
let mut aliases = BTreeMap::new();
let mut groups: BTreeMap<String, Group> = BTreeMap::new();
while let Some(event) = self .next() {
match event {
// ignores
ParseEvent::Text(_) => (),
ParseEvent::Start(ref name, _) if name == "comment" => self .skip_to_end("comment" ),
ParseEvent::Start(ref name, _) if name == "types" => self .skip_to_end("types" ),
// add group namespace
ParseEvent::Start(ref name, _) if name == "groups" => {
groups.extend(self .consume_groups(filter.api));
},
// add enum namespace
ParseEvent::Start(ref name, ref attributes) if name == "enums" => {
enums.extend(self .consume_enums(filter.api));
let enums_group = get_attribute(&attributes, "group" );
let enums_type = get_attribute(&attributes, "type" );
if let Some(group) = enums_group.and_then(|name| groups.get_mut(&name)) {
group.enums_type = enums_type;
}
},
// add command namespace
ParseEvent::Start(ref name, _) if name == "commands" => {
let (new_cmds, new_aliases) = self .consume_cmds(filter.api);
cmds.extend(new_cmds);
merge_map(&mut aliases, new_aliases);
},
ParseEvent::Start(ref name, ref attributes) if name == "feature" => {
debug!("Parsing feature: {:?}" , attributes);
features.push(Feature::convert(&mut self , &attributes));
},
ParseEvent::Start(ref name, _) if name == "extensions" => loop {
match self .next().unwrap() {
ParseEvent::Start(ref name, ref attributes) if name == "extension" => {
extensions.push(Extension::convert(&mut self , &attributes));
},
ParseEvent::End(ref name) if name == "extensions" => break ,
event => panic!("Unexpected message {:?}" , event),
}
},
// finished building the registry
ParseEvent::End(ref name) if name == "registry" => break ,
// error handling
event => panic!("Expected </registry>, found: {:?}" , event),
}
}
let mut desired_enums = BTreeSet::new();
let mut desired_cmds = BTreeSet::new();
// find the features we want
let mut found_feature = false ;
for feature in &features {
// XXX: verify that the string comparison with <= actually works as desired
if feature.api == filter.api && feature.number <= filter.version {
for require in &feature.requires {
desired_enums.extend(require.enums.iter().map(|x| x.clone()));
desired_cmds.extend(require.commands.iter().map(|x| x.clone()));
}
for remove in &feature.removes {
if remove.profile == filter.profile {
for enm in &remove.enums {
debug!("Removing {}" , enm);
desired_enums.remove(enm);
}
for cmd in &remove.commands {
debug!("Removing {}" , cmd);
desired_cmds.remove(cmd);
}
}
}
}
if feature.number == filter.version {
found_feature = true ;
}
}
if !found_feature && require_feature {
panic!("Did not find version {} in the registry" , filter.version);
}
for extension in &extensions {
if filter.extensions.contains(&extension.name) {
if !extension.supported.contains(&filter.api) {
panic!(
"Requested {}, which doesn't support the {} API" ,
extension.name, filter.api
);
}
for require in &extension.requires {
desired_enums.extend(require.enums.iter().map(|x| x.clone()));
desired_cmds.extend(require.commands.iter().map(|x| x.clone()));
}
}
}
let is_desired_enum = |e: &Enum | {
desired_enums.contains(&("GL_" .to_string() + &e.ident))
|| desired_enums.contains(&("WGL_" .to_string() + &e.ident))
|| desired_enums.contains(&("GLX_" .to_string() + &e.ident))
|| desired_enums.contains(&("EGL_" .to_string() + &e.ident))
};
let is_desired_cmd = |c: &Cmd| {
desired_cmds.contains(&("gl" .to_string() + &c.proto.ident))
|| desired_cmds.contains(&("wgl" .to_string() + &c.proto.ident))
|| desired_cmds.contains(&("glX" .to_string() + &c.proto.ident))
|| desired_cmds.contains(&("egl" .to_string() + &c.proto.ident))
};
Registry {
api: filter.api,
enums: enums.into_iter().filter(is_desired_enum).collect(),
cmds: cmds.into_iter().filter(is_desired_cmd).collect(),
aliases: if filter.fallbacks == Fallbacks::None {
BTreeMap::new()
} else {
aliases
},
groups,
}
}
fn consume_characters(&mut self ) -> String {
match self .next().unwrap() {
ParseEvent::Text(ch) => ch,
event => panic!("Expected characters, found: {:?}" , event),
}
}
fn consume_start_element(&mut self , expected_name: &str) -> Vec<Attribute> {
match self .next().unwrap() {
ParseEvent::Start(name, attributes) => {
if expected_name == name {
attributes
} else {
panic!("Expected <{}>, found: <{}>" , expected_name, name)
}
},
event => panic!("Expected <{}>, found: {:?}" , expected_name, event),
}
}
fn consume_end_element(&mut self , expected_name: &str) {
match self .next().unwrap() {
ParseEvent::End(ref name) if expected_name == name => (),
event => panic!("Expected </{}>, found: {:?}" , expected_name, event),
}
}
fn skip_to_end(&mut self , expected_name: &str) {
loop {
match self .next().unwrap() {
ParseEvent::End(ref name) if expected_name == name => break ,
_ => {},
}
}
}
fn consume_two<'a, T: FromXml, U: FromXml>(
&mut self ,
one: &'a str,
two: &'a str,
end: &'a str,
) -> (Vec<T>, Vec<U>) {
debug!("consume_two: looking for {} and {} until {}" , one, two, end);
let mut ones = Vec::new();
let mut twos = Vec::new();
loop {
match self .next().unwrap() {
ParseEvent::Start(ref name, ref attributes) => {
debug!("Found start element <{:?} {:?}>" , name, attributes);
debug!("one and two are {} and {}" , one, two);
let n = name.clone();
if one == n {
ones.push(FromXml::convert(self , &attributes));
} else if "type" == n {
// XXX: GL1.1 contains types, which we never care about anyway.
// Make sure consume_two doesn't get used for things which *do*
// care about type.
warn!("Ignoring type!" );
continue ;
} else if two == n {
twos.push(FromXml::convert(self , &attributes));
} else {
panic!("Unexpected element: <{:?} {:?}>" , n, &attributes);
}
},
ParseEvent::End(ref name) => {
debug!("Found end element </{:?}>" , name);
if one == name || two == name {
continue ;
} else if "type" == name {
// XXX: GL1.1 contains types, which we never care about anyway.
// Make sure consume_two doesn't get used for things which *do*
// care about type.
warn!("Ignoring type!" );
continue ;
} else if end == name {
return (ones, twos);
} else {
panic!("Unexpected end element {:?}" , name);
}
},
event => panic!("Unexpected message {:?}" , event),
}
}
}
fn consume_enums(&mut self , api: Api) -> Vec<Enum > {
let mut enums = Vec::new();
loop {
match self .next().unwrap() {
// ignores
ParseEvent::Text(_) => {},
ParseEvent::Start(ref name, _) if name == "unused" => self .skip_to_end("unused" ),
// add enum definition
ParseEvent::Start(ref name, ref attributes) if name == "enum" => {
enums.push(self .consume_enum(api, attributes));
},
// finished building the namespace
ParseEvent::End(ref name) if name == "enums" => break ,
// error handling
event => panic!("Expected </enums>, found: {:?}" , event),
}
}
enums
}
fn consume_enum(&mut self , api: Api, attributes: &[Attribute]) -> Enum {
let ident = trim_enum_prefix(&get_attribute(&attributes, "name" ).unwrap(), api).to_string();
let value = get_attribute(&attributes, "value" ).unwrap();
let alias = get_attribute(&attributes, "alias" );
let ty = get_attribute(&attributes, "type" );
self .consume_end_element("enum" );
match api {
Api::Egl => make_egl_enum(ident, ty, value, alias),
_ => make_enum(ident, ty, value, alias),
}
}
fn consume_groups(&mut self , api: Api) -> BTreeMap<String, Group> {
let mut groups = BTreeMap::new();
loop {
match self .next().unwrap() {
ParseEvent::Start(ref name, ref attributes) if name == "group" => {
let ident = get_attribute(&attributes, "name" ).unwrap();
let group = Group {
ident: ident.clone(),
enums_type: None,
enums: self .consume_group_enums(api)
};
groups.insert(ident, group);
},
ParseEvent::End(ref name) if name == "groups" => break ,
event => panic!("Expected </groups>, found: {:?}" , event),
}
}
groups
}
fn consume_group_enums(&mut self , api: Api) -> Vec<String> {
let mut enums = Vec::new();
loop {
match self .next().unwrap() {
ParseEvent::Start(ref name, ref attributes) if name == "enum" => {
let enum_name = get_attribute(&attributes, "name" );
enums.push(trim_enum_prefix(&enum_name.unwrap(), api));
self .consume_end_element("enum" );
},
ParseEvent::End(ref name) if name == "group" => break ,
event => panic!("Expected </group>, found: {:?}" , event),
}
}
enums
}
fn consume_cmds(&mut self , api: Api) -> (Vec<Cmd>, BTreeMap<String, Vec<String>>) {
let mut cmds = Vec::new();
let mut aliases: BTreeMap<String, Vec<String>> = BTreeMap::new();
loop {
match self .next().unwrap() {
// add command definition
ParseEvent::Start(ref name, _) if name == "command" => {
let new = self .consume_cmd(api);
if let Some(ref v) = new.alias {
match aliases.entry(v.clone()) {
Entry::Occupied(mut ent) => {
ent.get_mut().push(new.proto.ident.clone());
},
Entry::Vacant(ent) => {
ent.insert(vec![new.proto.ident.clone()]);
},
}
}
cmds.push(new);
},
// finished building the namespace
ParseEvent::End(ref name) if name == "commands" => break ,
// error handling
event => panic!("Expected </commands>, found: {:?}" , event),
}
}
(cmds, aliases)
}
fn consume_cmd(&mut self , api: Api) -> Cmd {
// consume command prototype
self .consume_start_element("proto" );
let mut proto = self .consume_binding("proto" , &[]);
proto.ident = trim_cmd_prefix(&proto.ident, api).to_string();
let mut params = Vec::new();
let mut alias = None;
let mut vecequiv = None;
let mut glx = None;
loop {
match self .next().unwrap() {
ParseEvent::Start(ref name, ref attributes) if name == "param" => {
params.push(self .consume_binding("param" , attributes));
},
ParseEvent::Start(ref name, ref attributes) if name == "alias" => {
alias = get_attribute(&attributes, "name" );
alias = alias.map(|t| trim_cmd_prefix(&t, api).to_string());
self .consume_end_element("alias" );
},
ParseEvent::Start(ref name, ref attributes) if name == "vecequiv" => {
vecequiv = get_attribute(&attributes, "vecequiv" );
self .consume_end_element("vecequiv" );
},
ParseEvent::Start(ref name, ref attributes) if name == "glx" => {
glx = Some(GlxOpcode {
opcode: get_attribute(&attributes, "opcode" ).unwrap(),
name: get_attribute(&attributes, "name" ),
});
self .consume_end_element("glx" );
},
ParseEvent::End(ref name) if name == "command" => break ,
event => panic!("Expected </command>, found: {:?}" , event),
}
}
Cmd {
proto: proto,
params: params,
alias: alias,
vecequiv: vecequiv,
glx: glx,
}
}
fn consume_binding(&mut self , outside_tag: &str, attributes: &[Attribute]) -> Binding {
// consume type
let mut ty = String::new();
loop {
match self .next().unwrap() {
ParseEvent::Text(text) => ty.push_str(&text),
ParseEvent::Start(ref name, _) if name == "ptype" => (),
ParseEvent::End(ref name) if name == "ptype" => (),
ParseEvent::Start(ref name, _) if name == "name" => break ,
event => panic!("Expected binding, found: {:?}" , event),
}
}
// consume identifier
let ident = underscore_keyword(self .consume_characters());
self .consume_end_element("name" );
// consume the type suffix
loop {
match self .next().unwrap() {
ParseEvent::Text(text) => ty.push_str(&text),
ParseEvent::End(ref name) if name == outside_tag => break ,
event => panic!("Expected binding, found: {:?}" , event),
}
}
Binding {
ident: ident,
ty: to_rust_ty(ty),
group: get_attribute(&attributes, "group" ),
}
}
}
impl <T> Parse for T where T: Sized + Iterator<Item = ParseEvent> {}
fn get_attribute(attribs: &[Attribute], key: &str) -> Option<String> {
attribs
.iter()
.find(|attrib| attrib.key == key)
.map(|attrib| attrib.value.clone())
}
trait FromXml {
fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Self ;
}
impl FromXml for Require {
fn convert<P: Parse>(parser: &mut P, _: &[Attribute]) -> Require {
debug!("Doing a FromXml on Require" );
let (enums, commands) = parser.consume_two("enum" , "command" , "require" );
Require {
enums: enums,
commands: commands,
}
}
}
impl FromXml for Remove {
fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Remove {
debug!("Doing a FromXml on Remove" );
let profile = get_attribute(a, "profile" ).unwrap();
let profile = profile_from_str(&profile).unwrap();
let (enums, commands) = parser.consume_two("enum" , "command" , "remove" );
Remove {
profile: profile,
enums: enums,
commands: commands,
}
}
}
impl FromXml for Feature {
fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Feature {
debug!("Doing a FromXml on Feature" );
let api = get_attribute(a, "api" ).unwrap();
let api = api_from_str(&api).unwrap().unwrap();
let name = get_attribute(a, "name" ).unwrap();
let number = get_attribute(a, "number" ).unwrap();
debug!("Found api = {}, name = {}, number = {}" , api, name, number);
let (require, remove) = parser.consume_two("require" , "remove" , "feature" );
Feature {
api: api,
name: name,
number: number,
requires: require,
removes: remove,
}
}
}
impl FromXml for Extension {
fn convert<P: Parse>(parser: &mut P, a: &[Attribute]) -> Extension {
debug!("Doing a FromXml on Extension" );
let name = get_attribute(a, "name" ).unwrap();
let supported = get_attribute(a, "supported" )
.unwrap()
.split('|' )
.filter_map(|api| {
api_from_str(api).unwrap_or_else(|()| panic!("unsupported API `{}`" , api))
})
.collect::<Vec<_>>();
let mut require = Vec::new();
loop {
match parser.next().unwrap() {
ParseEvent::Start(ref name, ref attributes) if name == "require" => {
require.push(FromXml::convert(parser, &attributes));
},
ParseEvent::End(ref name) if name == "extension" => break ,
event => panic!("Unexpected message {:?}" , event),
}
}
Extension {
name: name,
supported: supported,
requires: require,
}
}
}
impl FromXml for String {
fn convert<P: Parse>(_: &mut P, a: &[Attribute]) -> String {
get_attribute(a, "name" ).unwrap()
}
}
/// Converts a C style type definition to the Rust equivalent
pub fn to_rust_ty<T: AsRef<str>>(ty: T) -> Cow<'static, str> {
let ty = match ty.as_ref().trim() {
// gl.xml types
"GLDEBUGPROC" => "types::GLDEBUGPROC" ,
"GLDEBUGPROCAMD" => "types::GLDEBUGPROCAMD" ,
"GLDEBUGPROCARB" => "types::GLDEBUGPROCARB" ,
"GLDEBUGPROCKHR" => "types::GLDEBUGPROCKHR" ,
"GLbitfield" => "types::GLbitfield" ,
"GLboolean" => "types::GLboolean" ,
"GLbyte" => "types::GLbyte" ,
"GLclampd" => "types::GLclampd" ,
"GLclampf" => "types::GLclampf" ,
"GLclampx" => "types::GLclampx" ,
"GLdouble" => "types::GLdouble" ,
"GLeglImageOES" => "types::GLeglImageOES" ,
"GLenum" => "types::GLenum" ,
"GLfixed" => "types::GLfixed" ,
"GLfloat" => "types::GLfloat" ,
"GLhalfNV" => "types::GLhalfNV" ,
"GLhandleARB" => "types::GLhandleARB" ,
"GLint" => "types::GLint" ,
"GLint64" => "types::GLint64" ,
"GLint64EXT" => "types::GLint64EXT" ,
"GLintptr" => "types::GLintptr" ,
"GLintptrARB" => "types::GLintptrARB" ,
"GLshort" => "types::GLshort" ,
"GLsizei" => "types::GLsizei" ,
"GLsizeiptr" => "types::GLsizeiptr" ,
"GLsizeiptrARB" => "types::GLsizeiptrARB" ,
"GLsync" => "types::GLsync" ,
"GLubyte" => "types::GLubyte" ,
"GLuint" => "types::GLuint" ,
"GLuint64" => "types::GLuint64" ,
"GLuint64EXT" => "types::GLuint64EXT" ,
"GLushort" => "types::GLushort" ,
"GLvdpauSurfaceNV" => "types::GLvdpauSurfaceNV" ,
"void" => "()" ,
"GLboolean *" => "*mut types::GLboolean" ,
"GLchar *" => "*mut types::GLchar" ,
"const GLchar*" => "*const types::GLchar" ,
"GLcharARB *" => "*mut types::GLcharARB" ,
"GLdouble *" => "*mut types::GLdouble" ,
"GLenum *" => "*mut types::GLenum" ,
"GLfixed *" => "*mut types::GLfixed" ,
"GLfloat *" => "*mut types::GLfloat" ,
"GLhandleARB *" => "*mut types::GLhandleARB" ,
"GLint *" => "*mut types::GLint" ,
"GLint64 *" => "*mut types::GLint64" ,
"GLint64EXT *" => "*mut types::GLint64EXT" ,
"GLsizei *" => "*mut types::GLsizei" ,
"GLubyte *" => "*mut types::GLubyte" ,
"GLuint *" => "*mut types::GLuint" ,
"GLuint [2]" => "*mut [types::GLuint; 2]" ,
"GLuint64 *" => "*mut types::GLuint64" ,
"GLuint64EXT *" => "*mut types::GLuint64EXT" ,
"GLushort *" => "*mut types::GLushort" ,
"GLvoid *" => "*mut types::GLvoid" ,
"GLvoid **" => "*const *mut types::GLvoid" ,
"void *" => "*mut __gl_imports::raw::c_void" ,
"void **" => "*const *mut __gl_imports::raw::c_void" ,
"const GLboolean *" => "*const types::GLboolean" ,
"const GLbyte *" => "*const types::GLbyte" ,
"const GLchar *" => "*const types::GLchar" ,
"const GLcharARB *" => "*const types::GLcharARB" ,
"const GLclampf *" => "*const types::GLclampf" ,
"const GLdouble *" => "*const types::GLdouble" ,
"const GLenum *" => "*const types::GLenum" ,
"const GLfixed *" => "*const types::GLfixed" ,
"const GLfloat" => "types::GLfloat" ,
"const GLfloat *" => "*const types::GLfloat" ,
"const GLhalfNV *" => "*const types::GLhalfNV" ,
"const GLint *" => "*const types::GLint" ,
"const GLint*" => "*const types::GLint" ,
"const GLint64 *" => "*const types::GLint64" ,
"const GLint64EXT *" => "*const types::GLint64EXT" ,
"const GLintptr *" => "*const types::GLintptr" ,
"const GLshort *" => "*const types::GLshort" ,
"const GLsizei*" |
"const GLsizei *" => "*const types::GLsizei" ,
"const GLsizeiptr *" => "*const types::GLsizeiptr" ,
"const GLubyte *" => "*const types::GLubyte" ,
"const GLuint *" => "*const types::GLuint" ,
"const GLuint64 *" => "*const types::GLuint64" ,
"const GLuint64EXT *" => "*const types::GLuint64EXT" ,
"const GLushort *" => "*const types::GLushort" ,
"const GLvdpauSurfaceNV *" => "*const types::GLvdpauSurfaceNV" ,
"const GLvoid *" => "*const types::GLvoid" ,
"const void*" |
"const void *" => "*const __gl_imports::raw::c_void" ,
"const void **" => "*const *const __gl_imports::raw::c_void" ,
"const void *const*" => "*const *const __gl_imports::raw::c_void" ,
"const GLboolean **" => "*const *const types::GLboolean" ,
"const GLchar **" => "*const *const types::GLchar" ,
"const GLcharARB **" => "*const *const types::GLcharARB" ,
"const GLvoid **" => "*const *const types::GLvoid" ,
"const GLchar *const*" => "*const *const types::GLchar" ,
"const GLvoid *const*" => "*const *const types::GLvoid" ,
"struct _cl_context *" => "*const types::_cl_context" ,
"struct _cl_event *" => "*const types::_cl_event" ,
"GLuint[2]" => "[Gluint; 2]" ,
// glx.xml types
"Bool" => "types::Bool" ,
"Colormap" => "types::Colormap" ,
"DMbuffer" => "types::DMbuffer" ,
"Font" => "types::Font" ,
"GLXContext" => "types::GLXContext" ,
"GLXContextID" => "types::GLXContextID" ,
"GLXDrawable" => "types::GLXDrawable" ,
"GLXFBConfig" => "types::GLXFBConfig" ,
"GLXFBConfigSGIX" => "types::GLXFBConfigSGIX" ,
"GLXPbuffer" => "types::GLXPbuffer" ,
"GLXPbufferSGIX" => "types::GLXPbufferSGIX" ,
"GLXPixmap" => "types::GLXPixmap" ,
"GLXVideoCaptureDeviceNV" => "types::GLXVideoCaptureDeviceNV" ,
"GLXVideoDeviceNV" => "types::GLXVideoDeviceNV" ,
"GLXVideoSourceSGIX" => "types::GLXVideoSourceSGIX" ,
"GLXWindow" => "types::GLXWindow" ,
// "GLboolean" => "types::GLboolean",
// "GLenum" => "types::GLenum",
// "GLint" => "types::GLint",
// "GLsizei" => "types::GLsizei",
// "GLuint" => "types::GLuint",
"Pixmap" => "types::Pixmap" ,
"Status" => "types::Status" ,
"VLNode" => "types::VLNode" ,
"VLPath" => "types::VLPath" ,
"VLServer" => "types::VLServer" ,
"Window" => "types::Window" ,
"__GLXextFuncPtr" => "types::__GLXextFuncPtr" ,
"const GLXContext" => "const types::GLXContext" ,
"float" => "__gl_imports::raw::c_float" ,
"int" => "__gl_imports::raw::c_int" ,
"int64_t" => "i64" ,
"unsigned int" => "__gl_imports::raw::c_uint" ,
"unsigned long" => "__gl_imports::raw::c_ulong" ,
// "void " => "()",
"DMparams *" => "*mut types::DMparams" ,
"Display *" => "*mut types::Display" ,
"GLXFBConfig *" => "*mut types::GLXFBConfig" ,
"GLXFBConfigSGIX *" => "*mut types::GLXFBConfigSGIX" ,
"GLXHyperpipeConfigSGIX *" => "*mut types::GLXHyperpipeConfigSGIX" ,
"GLXHyperpipeNetworkSGIX *" => "*mut types::GLXHyperpipeNetworkSGIX" ,
"GLXVideoCaptureDeviceNV *" => "*mut types::GLXVideoCaptureDeviceNV" ,
"GLXVideoDeviceNV *" => "*mut types::GLXVideoDeviceNV" ,
// "GLuint *" => "*mut types::GLuint",
"XVisualInfo *" => "*mut types::XVisualInfo" ,
// "const GLubyte *" => "*GLubyte",
"const char *" => "*const __gl_imports::raw::c_char" ,
"const int *" => "*const __gl_imports::raw::c_int" ,
// "const void *" => "*const __gl_imports::raw::c_void",
"int *" => "*mut __gl_imports::raw::c_int" ,
"int32_t *" => "*mut i32" ,
"int64_t *" => "*mut i64" ,
"long *" => "*mut __gl_imports::raw::c_long" ,
"unsigned int *" => "*mut __gl_imports::raw::c_uint" ,
"unsigned long *" => "*mut __gl_imports::raw::c_ulong" ,
// "void *" => "*mut __gl_imports::raw::c_void",
// wgl.xml types
"BOOL" => "types::BOOL" ,
"DWORD" => "types::DWORD" ,
"FLOAT" => "types::FLOAT" ,
// "GLbitfield" => "types::GLbitfield",
// "GLboolean" => "types::GLboolean",
// "GLenum" => "types::GLenum",
// "GLfloat" => "types::GLfloat",
// "GLint" => "types::GLint",
// "GLsizei" => "types::GLsizei",
// "GLuint" => "types::GLuint",
// "GLushort" => "types::GLushort",
"HANDLE" => "types::HANDLE" ,
"HDC" => "types::HDC" ,
"HENHMETAFILE" => "types::HENHMETAFILE" ,
"HGLRC" => "types::HGLRC" ,
"HGPUNV" => "types::HGPUNV" ,
"HPBUFFERARB" => "types::HPBUFFERARB" ,
"HPBUFFEREXT" => "types::HPBUFFEREXT" ,
"HPVIDEODEV" => "types::HPVIDEODEV" ,
"HVIDEOINPUTDEVICENV" => "types::HVIDEOINPUTDEVICENV" ,
"HVIDEOOUTPUTDEVICENV" => "types::HVIDEOOUTPUTDEVICENV" ,
"INT" => "types::INT" ,
"INT64" => "types::INT64" ,
"LPCSTR" => "types::LPCSTR" ,
"LPGLYPHMETRICSFLOAT" => "types::LPGLYPHMETRICSFLOAT" ,
"LPVOID" => "types::LPVOID" ,
"PGPU_DEVICE" => "types::PGPU_DEVICE" ,
"PROC" => "types::PROC" ,
"UINT" => "types::UINT" ,
"VOID" => "types::VOID" ,
// "int " => "__gl_imports::raw::c_int",
// "unsigned int " => "__gl_imports::raw::c_uint",
// "void " => "()",
"BOOL *" => "*mut types::BOOL" ,
"DWORD *" => "*mut types::DWORD" ,
"FLOAT *" => "*mut types::FLOAT" ,
// "GLuint *" => "*mut types::GLuint",
"HANDLE *" => "*mut types::HANDLE" ,
"HGPUNV *" => "*mut types::HGPUNV" ,
"HPVIDEODEV *" => "*mut types::HPVIDEODEV" ,
"HVIDEOINPUTDEVICENV *" => "*mut types::HVIDEOINPUTDEVICENV" ,
"HVIDEOOUTPUTDEVICENV *" => "*mut types::HVIDEOOUTPUTDEVICENV" ,
"INT32 *" => "*mut types::INT32" ,
"INT64 *" => "*mut types::INT64" ,
"UINT *" => "*mut types::UINT" ,
"USHORT *" => "*mut types::USHORT" ,
"const COLORREF *" => "*const types::COLORREF" ,
"const DWORD *" => "*const types::DWORD" ,
"const FLOAT *" => "*const types::FLOAT" ,
// "const GLushort *" => "*const types::GLushort",
"const HANDLE *" => "*const types::HANDLE" ,
"const HGPUNV *" => "*const types::HGPUNV" ,
"const LAYERPLANEDESCRIPTOR *" => "*const types::LAYERPLANEDESCRIPTOR" ,
"const LPVOID *" => "*const types::LPVOID" ,
"const PIXELFORMATDESCRIPTOR *" => "*const types::IXELFORMATDESCRIPTOR" ,
"const USHORT *" => "*const types::USHORT" ,
// "const char *" => "*const __gl_imports::raw::c_char",
// "const int *" => "*const __gl_imports::raw::c_int",
"float *" => "*mut __gl_imports::raw::c_float" ,
// "int *" => "*mut __gl_imports::raw::c_int",
// "unsigned long *" => "*mut __gl_imports::raw::c_ulong",
// "void *" => "*mut __gl_imports::raw::c_void",
// elx.xml types
"khronos_utime_nanoseconds_t" => "types::khronos_utime_nanoseconds_t" ,
"khronos_uint64_t" => "types::khronos_uint64_t" ,
"khronos_ssize_t" => "types::khronos_ssize_t" ,
"EGLNativeDisplayType" => "types::EGLNativeDisplayType" ,
"EGLNativePixmapType" => "types::EGLNativePixmapType" ,
"EGLNativeWindowType" => "types::EGLNativeWindowType" ,
"EGLint" => "types::EGLint" ,
"EGLint *" => "*mut types::EGLint" ,
"const EGLint *" => "*const types::EGLint" ,
"NativeDisplayType" => "types::NativeDisplayType" ,
"NativePixmapType" => "types::NativePixmapType" ,
"NativeWindowType" => "types::NativeWindowType" ,
//"Bool" => "types::Bool",
"EGLBoolean" => "types::EGLBoolean" ,
"EGLenum" => "types::EGLenum" ,
"EGLAttribKHR" => "types::EGLAttribKHR" ,
"EGLAttrib" => "types::EGLAttrib" ,
"EGLAttrib *" => "*mut types::EGLAttrib" ,
"const EGLAttrib *" => "*const types::EGLAttrib" ,
"const EGLattrib *" => "*const types::EGLAttrib" , // Due to a typo in khronos_api/api_angle/scripts/egl_angle_ext.xml - see brendanzab/gl-rs#491
"EGLConfig" => "types::EGLConfig" ,
"EGLConfig *" => "*mut types::EGLConfig" ,
"EGLContext" => "types::EGLContext" ,
"EGLDeviceEXT" => "types::EGLDeviceEXT" ,
"EGLDisplay" => "types::EGLDisplay" ,
"EGLSurface" => "types::EGLSurface" ,
"EGLClientBuffer" => "types::EGLClientBuffer" ,
"__eglMustCastToProperFunctionPointerType" => {
"types::__eglMustCastToProperFunctionPointerType"
},
"EGLImageKHR" => "types::EGLImageKHR" ,
"EGLImage" => "types::EGLImage" ,
"EGLOutputLayerEXT" => "types::EGLOutputLayerEXT" ,
"EGLOutputPortEXT" => "types::EGLOutputPortEXT" ,
"EGLSyncKHR" => "types::EGLSyncKHR" ,
"EGLSync" => "types::EGLSync" ,
"EGLTimeKHR" => "types::EGLTimeKHR" ,
"EGLTime" => "types::EGLTime" ,
"EGLSyncNV" => "types::EGLSyncNV" ,
"EGLTimeNV" => "types::EGLTimeNV" ,
"EGLuint64NV" => "types::EGLuint64NV" ,
"EGLStreamKHR" => "types::EGLStreamKHR" ,
"EGLuint64KHR" => "types::EGLuint64KHR" ,
"EGLNativeFileDescriptorKHR" => "types::EGLNativeFileDescriptorKHR" ,
"EGLsizeiANDROID" => "types::EGLsizeiANDROID" ,
"EGLSetBlobFuncANDROID" => "types::EGLSetBlobFuncANDROID" ,
"EGLGetBlobFuncANDROID" => "types::EGLGetBlobFuncANDROID" ,
"EGLClientPixmapHI" => "types::EGLClientPixmapHI" ,
"struct EGLClientPixmapHI *" => "*const types::EGLClientPixmapHI" ,
"const EGLAttribKHR *" => "*const types::EGLAttribKHR" ,
"const EGLuint64KHR *" => "*const types::EGLuint64KHR" ,
"EGLAttribKHR *" => "*mut types::EGLAttribKHR" ,
"EGLDeviceEXT *" => "*mut types::EGLDeviceEXT" ,
"EGLNativeDisplayType *" => "*mut types::EGLNativeDisplayType" ,
"EGLNativePixmapType *" => "*mut types::EGLNativePixmapType" ,
"EGLNativeWindowType *" => "*mut types::EGLNativeWindowType" ,
"EGLOutputLayerEXT *" => "*mut types::EGLOutputLayerEXT" ,
"EGLTimeKHR *" => "*mut types::EGLTimeKHR" ,
"EGLOutputPortEXT *" => "*mut types::EGLOutputPortEXT" ,
"EGLuint64KHR *" => "*mut types::EGLuint64KHR" ,
"const struct AHardwareBuffer *" => "*const __gl_imports::raw::c_void" , // humm
"GLeglClientBufferEXT" => "types::GLeglClientBufferEXT" ,
"GLVULKANPROCNV" => "types::GLVULKANPROCNV" ,
"EGLDEBUGPROCKHR" => "types::EGLDEBUGPROCKHR" ,
"EGLObjectKHR" => "types::EGLObjectKHR" ,
"EGLLabelKHR" => "types::EGLLabelKHR" ,
"EGLnsecsANDROID" => "types::EGLnsecsANDROID" ,
"EGLnsecsANDROID *" => "*mut types::EGLnsecsANDROID" ,
"EGLBoolean *" => "*mut types::EGLBoolean" ,
// failure
_ => panic!("Type conversion not implemented for `{}`" , ty.as_ref()),
};
Cow::Borrowed(ty)
}
#[ cfg(test)]
mod tests {
mod underscore_numeric_prefix {
use registry::parse;
#[ test]
fn test_numeric_prefix() {
assert_eq!(parse::underscore_numeric_prefix("3" ), "_3" );
assert_eq!(parse::underscore_numeric_prefix("123_FOO" ), "_123_FOO" );
}
#[ test]
fn test_non_numeric_prefix() {
assert_eq!(parse::underscore_numeric_prefix("" ), "" );
assert_eq!(parse::underscore_numeric_prefix("A" ), "A" );
assert_eq!(parse::underscore_numeric_prefix("FOO" ), "FOO" );
}
}
mod underscore_keyword {
use registry::parse;
#[ test]
fn test_keyword() {
assert_eq!(parse::underscore_keyword("in" .to_string()), "in_" );
assert_eq!(parse::underscore_keyword("ref" .to_string()), "ref_" );
assert_eq!(parse::underscore_keyword("type" .to_string()), "type_" );
}
#[ test]
fn test_non_keyword() {
assert_eq!(parse::underscore_keyword("foo" .to_string()), "foo" );
assert_eq!(parse::underscore_keyword("bar" .to_string()), "bar" );
}
}
mod make_enum {
use registry::parse;
#[ test]
fn test_cast_0() {
let e = parse::make_enum(
"FOO" .to_string(),
None,
"((EGLint)-1)" .to_string(),
Some("BAR" .to_string()),
);
assert_eq!(e.ident, "FOO" );
assert_eq!((&*e.ty, &*e.value), ("EGLint" , "-1" ));
assert_eq!(e.alias, Some("BAR" .to_string()));
}
#[ test]
fn test_cast_1() {
let e = parse::make_enum(
"FOO" .to_string(),
None,
"((EGLint)(-1))" .to_string(),
Some("BAR" .to_string()),
);
assert_eq!(e.ident, "FOO" );
assert_eq!((&*e.ty, &*e.value), ("EGLint" , "(-1)" ));
assert_eq!(e.alias, Some("BAR" .to_string()));
}
#[ test]
fn test_no_type() {
let e = parse::make_enum(
"FOO" .to_string(),
None,
"value" .to_string(),
Some("BAR" .to_string()),
);
assert_eq!(e.ident, "FOO" );
assert_eq!(e.value, "value" );
assert_eq!(e.alias, Some("BAR" .to_string()));
assert_eq!(e.ty, "GLenum" );
assert_eq!(e.cast, false );
}
#[ test]
fn test_u() {
let e = parse::make_enum(
"FOO" .to_string(),
Some("u" .to_string()),
String::new(),
None,
);
assert_eq!(e.ty, "GLuint" );
}
#[ test]
fn test_ull() {
let e = parse::make_enum(
"FOO" .to_string(),
Some("ull" .to_string()),
String::new(),
None,
);
assert_eq!(e.ty, "GLuint64" );
}
#[ test]
#[ should_panic]
fn test_unknown_type() {
parse::make_enum(
"FOO" .to_string(),
Some("blargh" .to_string()),
String::new(),
None,
);
}
#[ test]
fn test_value_str() {
let e = parse::make_enum("FOO" .to_string(), None, "\" hi\"" .to_string(), None);
assert_eq!(e.ty, "&'static str" );
}
#[ test]
fn test_ident_true() {
let e = parse::make_enum("TRUE" .to_string(), None, String::new(), None);
assert_eq!(e.ty, "GLboolean" );
}
#[ test]
fn test_ident_false() {
let e = parse::make_enum("FALSE" .to_string(), None, String::new(), None);
assert_eq!(e.ty, "GLboolean" );
}
}
mod make_egl_enum {
use registry::parse;
#[ test]
fn test_cast_egl() {
let e = parse::make_egl_enum(
"FOO" .to_string(),
None,
"EGL_CAST(EGLint,-1)" .to_string(),
Some("BAR" .to_string()),
);
assert_eq!(e.ident, "FOO" );
assert_eq!((&*e.ty, &*e.value), ("EGLint" , "-1" ));
assert_eq!(e.alias, Some("BAR" .to_string()));
}
#[ test]
fn test_ident_true() {
let e = parse::make_egl_enum("TRUE" .to_string(), None, "1234" .to_string(), None);
assert_eq!(e.ty, "EGLBoolean" );
}
#[ test]
fn test_ident_false() {
let e = parse::make_egl_enum("FALSE" .to_string(), None, "1234" .to_string(), None);
assert_eq!(e.ty, "EGLBoolean" );
}
#[ test]
fn test_ull() {
let e = parse::make_egl_enum(
"FOO" .to_string(),
Some("ull" .to_string()),
"1234" .to_string(),
None,
);
assert_eq!(e.ty, "EGLuint64KHR" );
}
#[ test]
fn test_negative_value() {
let e = parse::make_egl_enum("FOO" .to_string(), None, "-1" .to_string(), None);
assert_eq!(e.ty, "EGLint" );
}
#[ test]
#[ should_panic]
fn test_unknown_type() {
parse::make_egl_enum(
"FOO" .to_string(),
Some("blargh" .to_string()),
String::new(),
None,
);
}
#[ test]
#[ should_panic]
fn test_unknown_value() {
parse::make_egl_enum("FOO" .to_string(), None, "a" .to_string(), None);
}
#[ test]
#[ should_panic]
fn test_empty_value() {
parse::make_egl_enum("FOO" .to_string(), None, String::new(), None);
}
}
mod parse_event {
mod from_xml {
use xml::attribute::OwnedAttribute;
use xml::common::XmlVersion;
use xml::name::OwnedName;
use xml::namespace::Namespace;
use xml::reader::XmlEvent;
use registry::parse::{Attribute, ParseEvent};
#[ test]
fn test_start_event() {
let given = XmlEvent::StartElement {
name: OwnedName::local("element" ),
attributes: vec![
OwnedAttribute::new(OwnedName::local("attr1" ), "val1" ),
OwnedAttribute::new(OwnedName::local("attr2" ), "val2" ),
],
namespace: Namespace::empty(),
};
let expected = ParseEvent::Start(
"element" .to_string(),
vec![
Attribute::new("attr1" , "val1" ),
Attribute::new("attr2" , "val2" ),
],
);
assert_eq!(ParseEvent::from_xml(given), Some(expected));
}
#[ test]
fn test_end_element() {
let given = XmlEvent::EndElement {
name: OwnedName::local("element" ),
};
let expected = ParseEvent::End("element" .to_string());
assert_eq!(ParseEvent::from_xml(given), Some(expected));
}
#[ test]
fn test_characters() {
let given = XmlEvent::Characters("text" .to_string());
let expected = ParseEvent::Text("text" .to_string());
assert_eq!(ParseEvent::from_xml(given), Some(expected));
}
#[ test]
fn test_start_document() {
let given = XmlEvent::StartDocument {
version: XmlVersion::Version10,
encoding: "" .to_string(),
standalone: None,
};
assert_eq!(ParseEvent::from_xml(given), None);
}
#[ test]
fn test_end_document() {
let given = XmlEvent::EndDocument;
assert_eq!(ParseEvent::from_xml(given), None);
}
#[ test]
fn test_processing_instruction() {
let given = XmlEvent::ProcessingInstruction {
name: "" .to_string(),
data: None,
};
assert_eq!(ParseEvent::from_xml(given), None);
}
#[ test]
fn test_cdata() {
let given = XmlEvent::CData("CData" .to_string());
assert_eq!(ParseEvent::from_xml(given), None);
}
#[ test]
fn test_comment() {
let given = XmlEvent::Comment("Comment" .to_string());
assert_eq!(ParseEvent::from_xml(given), None);
}
#[ test]
fn test_whitespace() {
let given = XmlEvent::Whitespace("Whitespace" .to_string());
assert_eq!(ParseEvent::from_xml(given), None);
}
}
}
}
Messung V0.5 in Prozent C=94 H=92 G=92
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-06-21)
¤
*© Formatika GbR, Deutschland