Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Threema/libthreema/macros/src/   (Social Medium ©)  Datei vom 26.9.2026 mit Größe 25 kB image not shown  

Quelle  lib.rs   Sprache: Rust

 

//! Commonly used macros of libthreema.
use convert_case::{Case, Casing as _};
use proc_macro::TokenStream;
use quote::{ToTokens as _, format_ident, quote};
use syn::{
    self, Data, DeriveInput, Expr, Fields, Ident, ItemStruct, LitInt, LitStr, Variant,
    parse::{Parse, ParseStream},
    parse_macro_input, parse_quote,
    punctuated::Punctuated,
};

/// Provides the name of a `struct`, `enum` or `union`.
///
/// # Examples
///
/// Given the following:
///
/// ```nobuild
/// use libthreema_macros::Name;
/// use crate::utils::debug::Name;
///
/// #[derive(Name)]
/// struct Something;
/// ```
///
/// the derive macro expands it to:
///
/// ```nobuild
/// struct Something;
/// impl Name for Something {
///     const NAME: &'static str = "Something";
/// }
/// ```
#[proc_macro_derive(Name)]
pub fn derive_name(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);

    // Implement `NAME`
    let name = input.ident;
    let literal_name = name.to_string();
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
    let expanded = quote! {
        impl #impl_generics crate::utils::debug::Name for #name #type_generics #where_clause {
            /// The name for debugging purposes
            const NAME: &'static str = #literal_name;
        }
    };

    // Generate code
    TokenStream::from(expanded)
}

/// Invokes `uniffi::custom_newtype!()` for the derived item.
///
/// Note: This is only applicable for new-type structs with data types supported by UniFFI.
///
/// # Examples
///
/// ## Basic Usage
///
/// ```nobuild
/// /// This is my awesome struct.
/// #[derive(UniffiNewType)]
/// pub struct MyStruct(pub u64);
/// ```
///
/// The derive macro expands it to:
///
/// ```nobuild
/// /// This is my awesome struct.
/// pub struct MyStruct(pub u64);
/// uniffi::custom_newtype!(
///     /// This is my awesome struct.
///     MyStruct,
///     u64
/// );
/// ```
///
/// ## Fixed-Size Bytes
///
/// One can specify conversion to and from fixed size bytes:
///
/// ```nobuild
/// pub struct MyFoo(pub [u8; Self::LENGTH]);
/// impl MyFoo {
///     pub const LENGTH: usize = 32;
/// }
///
/// /// Binding version of [`MyFoo`].
/// #[uniffi_new_type(fixed_size_bytes = MyFoo)]
/// pub struct MyFooBindingVersion(pub Vec<u8>);
/// ```
///
/// Note: The constant `LENGTH` must be present on the struct!
///
/// The derive macro expands it to:
/// ```nobuild
/// /// Binding version of [`MyFoo`].
/// pub struct MyFooBindingVersion(pub Vec<u8>);
/// uniffi::custom_newtype!(
///     /// Binding version of [`MyFoo`].
///     MyFooBindingVersion,
///     Vec<u8>
/// );
/// impl From<MyFoo> for MyFooBindingVersion {
///     fn from(value: MyFoo) -> MyFooBindingVersion {
///         MyFooBindingVersion(value.0.to_vec())
///     }
/// }
/// impl TryFrom<MyFooBindingVersion> for MyFoo {
///     type Error = crate::bindings::uniffi::ConversionError;
///
///     fn try_from(value: MyFooBindingVersion) -> ::core::result::Result<Self, Self::Error> {
///         Self::try_from(value.0.as_slice()).map_err(|_| {
///             crate::bindings::uniffi::ConversionError::ContractViolation {
///                 name: "MyFoo".to_owned(),
///                 reason: format!("Must be {} bytes", Self::LENGTH),
///             }
///         })
///     }
/// }
/// ```
///
/// ## String
///
/// One can specify conversion to and from a string:
///
/// ```nobuild
/// pub struct MyFoo(String);
/// impl MyFoo {
///     pub fn as_str(&self) -> &str {
///         &self.0
///     }
/// }
/// impl TryFrom<String> for MyFoo {
///     type Error = MyFooError;
///
///     fn try_from(value: String) -> Result<Self, Self::Error> {
///         // Validate and construct ...
///     }
/// }
///
/// /// Binding version of [`MyFoo`].
/// #[uniffi_new_type(string = MyFoo)]
/// pub struct MyFooBindingVersion(pub String);
/// ```
///
/// Note: `MyFoo` must expose an `as_str()` method and implement `TryFrom<String>` whose error implements
/// [`ToString`].
///
/// The derive macro expands it to:
/// ```nobuild
/// /// Binding version of [`MyFoo`].
/// pub struct MyFooBindingVersion(pub String);
/// uniffi::custom_newtype!(
///     /// Binding version of [`MyFoo`].
///     MyFooBindingVersion,
///     String
/// );
/// impl From<MyFoo> for MyFooBindingVersion {
///     fn from(value: MyFoo) -> MyFooBindingVersion {
///         MyFooBindingVersion(value.as_str().to_owned())
///     }
/// }
/// impl TryFrom<MyFooBindingVersion> for MyFoo {
///     type Error = crate::bindings::uniffi::ConversionError;
///
///     fn try_from(value: MyFooBindingVersion) -> ::core::result::Result<Self, Self::Error> {
///         Self::try_from(value.0).map_err(|error| {
///             crate::bindings::uniffi::ConversionError::ContractViolation {
///                 name: "MyFoo".to_owned(),
///                 reason: error.to_string(),
///             }
///         })
///     }
/// }
/// ```
#[proc_macro_derive(UniffiNewType, attributes(uniffi_new_type))]
pub fn derive_uniffi_new_type(input: TokenStream) -> TokenStream {
    fn annotate_uniffi_new_type(input: DeriveInput) -> syn::Result<TokenStream> {
        let name = input.ident;

        #[expect(
            clippy::unimplemented,
            reason = "Only applicable to new-type structs with exactly one field"
        )]
        let inner_type = match &input.data {
            Data::Struct(data) => match &data.fields {
                Fields::Unnamed(fields) => {
                    if fields.unnamed.len() != 1 {
                        unimplemented!()
                    }
                    let Some(field) = fields.unnamed.first() else {
                        unimplemented!()
                    };
                    field.ty.clone()
                },
                _ => unimplemented!(),
            },
            _ => unimplemented!(),
        };

        // Declare the new type.
        let mut expanded = {
            let documentation = input
                .attrs
                .iter()
                .filter(|attribute| attribute.path().is_ident("doc"))
                .collect::<Vec<_>>();
            quote! { uniffi::custom_newtype!(
                #(#documentation)*
                #name,
                #inner_type
            ); }
        };

        // Extract the path of the new-type it should refer to.
        for attribute in input.attrs {
            #[expect(clippy::unimplemented, reason = "Unsupported attribute")]
            if attribute.path().is_ident("uniffi_new_type") {
                attribute.parse_nested_meta(|meta| {
                    let path = meta.value()?.parse::<syn::Path>()?;
                    match meta.path.get_ident().map(ToString::to_string).as_deref() {
                        Some("fixed_size_bytes") => {
                            // Implement conversion from/to the fixed size bytes newtype.
                            expanded.extend(quote! {
                                impl From<#path> for #name {
                                    fn from(value: #path) -> Self {
                                        Self(value.0.to_vec())
                                    }
                                }

                                impl TryFrom<#name> for #path {
                                    type Error = crate::bindings::uniffi::ConversionError;

                                    fn try_from(value: #name) -> ::core::result::Result<Self, Self::Error> {
                                        Self::try_from(value.0.as_slice()).map_err(|_| {
                                            crate::bindings::uniffi::ConversionError::ContractViolation {
                                                name: stringify!(#name).to_owned(),
                                                reason: format!("Must be {} bytes", Self::LENGTH),
                                            }
                                        })
                                    }
                                }
                            });
                        },
                        Some("string") => {
                            // Implement conversion from/to the String newtype.
                            expanded.extend(quote! {
                                impl From<#path> for #name {
                                    fn from(value: #path) -> Self {
                                        Self(value.as_str().to_owned())
                                    }
                                }

                                impl TryFrom<#name> for #path {
                                    type Error = crate::bindings::uniffi::ConversionError;

                                    fn try_from(value: #name) -> ::core::result::Result<Self, Self::Error> {
                                        Self::try_from(value.0).map_err(|error| {
                                            crate::bindings::uniffi::ConversionError::ContractViolation {
                                                name: stringify!(#name).to_owned(),
                                                reason: error.to_string(),
                                            }
                                        })
                                    }
                                }
                            });
                        },
                        _ => unimplemented!(),
                    }

                    Ok(())
                })?;
            }
        }

        Ok(TokenStream::from(expanded))
    }
    annotate_uniffi_new_type(parse_macro_input!(input))
        .unwrap_or_else(|error| error.into_compile_error().into())
}

/// Generates a UniFFI-compatible binding enum for a `Delta`.
///
/// # Examples
///
/// ```nobuild
/// uniffi_define_delta!(path::to::Foo, DeltaFoo);
/// ```
///
/// This will expand to:
///
/// ```nobuild
/// #[derive(uniffi::Enum)]
/// pub enum DeltaFoo {
///     Unchanged,
///     Update(path::to::Foo),
///     Remove,
/// }
/// impl From<crate::utils::update::Delta<path::to::Foo>> for DeltaFoo {
///     fn from(delta: crate::utils::update::Delta<path::to::Foo>) -> Self {
///         match delta {
///             crate::utils::update::Delta::Unchanged => Self::Unchanged,
///             crate::utils::update::Delta::Update(value) => Self::Update(value.into()),
///             crate::utils::update::Delta::Remove => Self::Remove,
///         }
///     }
/// }
/// impl From<DeltaFoo> for crate::utils::update::Delta<path::to::Foo> {
///     fn from(delta: DeltaFoo) -> Self {
///         match delta {
///             DeltaFoo::Unchanged => Self::Unchanged,
///             DeltaFoo::Update(value) => Self::Update(value.into()),
///             DeltaFoo::Remove => Self::Remove,
///         }
///     }
/// }
/// ```
#[proc_macro]
pub fn uniffi_define_delta(input: TokenStream) -> TokenStream {
    struct UniffiDefineDeltaInput {
        inner_type: syn::Path,
        _comma: syn::Token![,],
        output_name: Ident,
    }
    impl Parse for UniffiDefineDeltaInput {
        fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
            Ok(Self {
                inner_type: input.parse()?,
                _comma: input.parse()?,
                output_name: input.parse()?,
            })
        }
    }

    let UniffiDefineDeltaInput {
        inner_type,
        _comma,
        output_name: delta_name,
    } = parse_macro_input!(input as UniffiDefineDeltaInput);

    let docstring = format!(
        "Binding version of [`crate::utils::update::Delta`] for [`{}`].",
        inner_type.segments.to_token_stream()
    );
    let expanded = quote! {
        #[doc = #docstring]
        #[derive(Default, uniffi::Enum)]
        #[expect(missing_docs, reason = "Binding version")]
        pub enum #delta_name {
            #[default]
            Unchanged,

            Update(#inner_type),

            Remove,
        }
        impl From<crate::utils::update::Delta<#inner_type>> for #delta_name {
            fn from(delta: crate::utils::update::Delta<#inner_type>) -> Self {
                match delta {
                    crate::utils::update::Delta::Unchanged => Self::Unchanged,
                    crate::utils::update::Delta::Update(value) => Self::Update(value),
                    crate::utils::update::Delta::Remove => Self::Remove,
                }
            }
        }
        impl From<#delta_name> for crate::utils::update::Delta<#inner_type> {
            fn from(delta: #delta_name) -> Self {
                match delta {
                    #delta_name::Unchanged => Self::Unchanged,
                    #delta_name::Update(value) => Self::Update(value),
                    #delta_name::Remove => Self::Remove,
                }
            }
        }
    };

    TokenStream::from(expanded)
}

/// Provides variant names for an `enum`.
///
/// # Examples
///
/// Given the following:
///
/// ```
/// use libthreema_macros::VariantNames;
///
/// #[derive(VariantNames)]
/// enum Something {
///     SomeItem,
///     SomeOtherItem(u64),
/// }
/// ```
///
/// the derive macro expands it to:
///
/// ```
/// enum Something {
///     SomeItem,
///     SomeOtherItem(u64),
/// }
///
/// impl Something {
///     pub const SOME_ITEM: &'static str = "SomeItem";
///     pub const SOME_OTHER_ITEM: &'static str = "SomeOtherItem";
///
///     pub const fn variant_name(&self) -> &'static str {
///         match self {
///             Self::SomeItem => Self::SOME_ITEM,
///             Self::SomeOtherItem(..) => Self::SOME_OTHER_ITEM,
///         }
///     }
/// }
/// ```
#[proc_macro_derive(VariantNames)]
pub fn derive_variant_names(input: TokenStream) -> TokenStream {
    fn get_const_name(variant: &Variant) -> Ident {
        format_ident!(
            "{}",
            variant
                .ident
                .to_string()
                .from_case(Case::Pascal)
                .to_case(Case::UpperSnake)
        )
    }

    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);
    let enum_name = input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();

    // Map each variant to its literal identifier name
    let const_variants = match &input.data {
        Data::Enum(data) => data.variants.iter().map(|variant| {
            let docstring = format!(" Variant name of [`{}::{}`].", enum_name, variant.ident);
            let const_name = get_const_name(variant);
            let literal_name = variant.ident.to_string();
            quote! {
                #[doc = #docstring]
                pub const #const_name: &'static str = #literal_name;
            }
        }),
        #[expect(clippy::unimplemented, reason = "Only applicable to enums")]
        _ => unimplemented!(),
    };
    let mapped_variants = match &input.data {
        Data::Enum(data) => data.variants.iter().map(|variant| {
            let variant_name = &variant.ident;
            let parameters = match variant.fields {
                Fields::Unit => quote! {},
                Fields::Unnamed(..) => quote! { (..) },
                Fields::Named(..) => quote! { {..} },
            };
            let const_name = get_const_name(variant);
            quote! {
                Self::#variant_name #parameters => Self::#const_name
            }
        }),
        #[expect(clippy::unimplemented, reason = "Only applicable to enums")]
        _ => unimplemented!(),
    };

    // Implement for the enum
    let expanded = quote! {
        impl #impl_generics #enum_name #type_generics #where_clause {
            #(#const_variants)*

            /// Get the variant name of `self`.
            pub const fn variant_name(&self) -> &'static str {
                match self {
                    #(#mapped_variants),*
                }
            }
        }
    };

    // Generate code
    TokenStream::from(expanded)
}

/// Implements [`Debug`] for the provided `enum`. Depends on [`VariantNames`].
///
/// # Examples
///
/// Given the following:
///
/// ```
/// use libthreema_macros::{DebugVariantNames, VariantNames};
///
/// #[derive(DebugVariantNames, VariantNames)]
/// enum Something {
///     SomeItem,
///     SomeOtherItem(u64),
/// }
/// ```
///
/// the derive macro expands it to:
///
/// ```
/// # use libthreema_macros::VariantNames;
/// #
/// # #[derive(VariantNames)]
/// enum Something {
///     SomeItem,
///     SomeOtherItem(u64),
/// }
///
/// // Omitting expansion of `VariantNames` here.
///
/// impl std::fmt::Debug for Something {
///     fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
///         write!(formatter, "{}::{}", "Something", self.variant_name())
///     }
/// }
/// ```
#[proc_macro_derive(DebugVariantNames)]
pub fn derive_debug_variant_names(input: TokenStream) -> TokenStream {
    // Parse the input tokens into a syntax tree.
    let input = parse_macro_input!(input as DeriveInput);

    // Ensure it's an enum
    #[expect(clippy::unimplemented, reason = "Only applicable to enums")]
    if !matches!(input.data, Data::Enum(..)) {
        unimplemented!()
    }

    // Implement `Debug` for the enum
    let name = input.ident;
    let literal_name = name.to_string();
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
    let expanded = quote! {
        impl #impl_generics std::fmt::Debug for #name #type_generics #where_clause {
            fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                write!(formatter, "{}::{}", #literal_name, self.variant_name())
            }
        }
    };

    // Generate code
    TokenStream::from(expanded)
}

struct Arrays(Punctuated<Expr, syn::Token![,]>);

impl Parse for Arrays {
    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
        let punctuated = Punctuated::parse_terminated(input)?;
        Ok(Self(punctuated))
    }
}

/// Concatenates fixed-size byte arrays into a single large byte array.
///
/// # Examples
///
/// ```
/// use libthreema_macros::concat_fixed_bytes;
///
/// let a = [1u8; 4];
/// let b = [2u8; 3];
/// let c = [3u8; 3];
///
/// let concatenated: [u8; 10] = concat_fixed_bytes!(a, b, c);
/// assert_eq!(concatenated, [1, 1, 1, 1, 2, 2, 2, 3, 3, 3]);
/// ```
#[proc_macro]
pub fn concat_fixed_bytes(tokens: TokenStream) -> TokenStream {
    let input = parse_macro_input!(tokens as Arrays).0.into_iter();
    let indices = input.clone().enumerate();
    let arrays: Vec<Expr> = input.collect();
    let field_length_parameters: Vec<Ident> = indices
        .clone()
        .map(|(index, _)| format_ident!("T{index}"))
        .collect();
    let field_names: Vec<Ident> = indices.map(|(index, _)| format_ident!("t{index}")).collect();

    let expanded = quote! {{
        #[repr(C)]
        struct ConcatenatedArrays<#(const #field_length_parameters: usize,)*> {
            #(#field_names: [u8; #field_length_parameters],)*
        }
        let concatenated_arrays = ConcatenatedArrays {
            #(#field_names: #arrays,)*
        };
        unsafe {
            let concatenated_bytes = core::mem::transmute(concatenated_arrays);
            concatenated_bytes
        }
    }};

    // Generate code
    TokenStream::from(expanded)
}

/// Annotates a `prost::Message` in the following way:
///
/// ## `padding` fields
///
/// These fields will be marked as deprecated to discourage direct usage of it. Furthermore, the padding tag
/// will be extracted and made available on the message as a `PADDING_TAG` const. See the
/// `protobuf::PaddedMessage` trait.
#[proc_macro_attribute]
pub fn protobuf_annotations(_attribute: TokenStream, input: TokenStream) -> TokenStream {
    fn annotate_protobuf_message(mut message: ItemStruct) -> syn::Result<TokenStream> {
        let mut padding_tag: Option<LitInt> = None;

        for field in &mut message.fields {
            let Some(name) = field.ident.as_ref() else {
                continue;
            };

            // Process `padding` fields so that we can use `ProtobufPaddedMessage` on them easily
            if name == "padding" {
                // Look for the tag value in `#[prost(..., tag = "<tag-value>")]`
                for attribute in &field.attrs {
                    if !attribute.path().is_ident("prost") {
                        continue;
                    }
                    attribute.parse_nested_meta(|meta| {
                        let value: LitStr = meta.value()?.parse()?;
                        if meta.path.is_ident("tag") {
                            padding_tag = Some(value.parse()?);
                        }
                        Ok(())
                    })?;
                }

                // Ensure nobody uses the field directly by deprecating it
                field.attrs.push(parse_quote! {
                    #[deprecated(note = "Use ProtobufPaddedMessage trait to generate padding")]
                });
            }
        }

        let message_name = message.ident.clone();
        let mut output = message.into_token_stream();

        // Add any padding tag value as a const to the message
        if let Some(padding_tag) = padding_tag {
            output.extend(quote! {
                impl #message_name {
                    /// Tag value of the padding of this message.
                    pub const PADDING_TAG: u32 = #padding_tag;
                }
            });
        }

        Ok(output.into())
    }
    annotate_protobuf_message(parse_macro_input!(input as ItemStruct))
        .unwrap_or_else(|error| error.into_compile_error().into())
}

/// Implements [`libthreema_dependencies::crypto::subtle::ConstantTimeEq`] for named and unnamed structs.
///
/// Moreover, this derives [`PartialEq`] and [`Eq`] using constant time comparison.
///
/// Note: All fields must implement [`libthreema_dependencies::crypto::subtle::ConstantTimeEq`].
///
/// The proc macro was adapted from <https://github.com/dalek-cryptography/subtle/pull/111>.
///
/// # Examples
///
/// Given the following:
///
/// ```
/// use libthreema_macros::ConstantTimeEq;
///
/// #[derive(ConstantTimeEq)]
/// struct MyStruct {
///     first_field: [u8; 32],
///     second_field: u64,
/// }
/// ```
///
/// the derive macro expands it to:
///
/// ```
/// struct MyStruct {
///     first_field: [u8; 32],
///     second_field: u64,
/// }
///
/// impl ::libthreema_dependencies::crypto::subtle::ConstantTimeEq for MyStruct {
///     #[inline]
///     fn ct_eq(&self, other: &Self) -> ::libthreema_dependencies::crypto::subtle::Choice {
///         use ::libthreema_dependencies::crypto::subtle::ConstantTimeEq as _;
///         return { self.first_field }.ct_eq(&{ other.first_field })
///             & { self.second_field }.ct_eq(&{ other.second_field });
///     }
/// }
/// impl PartialEq<Self> for MyStruct {
///     #[inline]
///     fn eq(&self, other: &Self) -> bool {
///         use ::libthreema_dependencies::crypto::subtle::ConstantTimeEq as _;
///         bool::from(self.ct_eq(other))
///     }
/// }
/// impl Eq for MyStruct {}
/// ```
#[proc_macro_derive(ConstantTimeEq)]
pub fn constant_time_eq(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    #[expect(
        clippy::unimplemented,
        reason = "Only applicable to named and unnamed structs"
    )]
    let Data::Struct(data_struct) = input.data else {
        unimplemented!()
    };

    let constant_time_eq_stream = match &data_struct.fields {
        Fields::Named(fields_named) => {
            let mut token_stream = quote! {};
            let mut fields = fields_named.named.iter().peekable();
            while let Some(field) = fields.next() {
                let ident = &field.ident;
                token_stream.extend(quote! { {self.#ident}.ct_eq(&{other.#ident}) });

                if fields.peek().is_some() {
                    token_stream.extend(quote! { & });
                }
            }
            token_stream
        },
        Fields::Unnamed(unnamed_fields) => {
            let mut token_stream = quote! {};
            let mut fields = unnamed_fields.unnamed.iter().enumerate().peekable();
            while let Some(field) = fields.next() {
                let index = syn::Index::from(field.0);
                token_stream.extend(quote! { {self.#index}.ct_eq(&{other.#index}) });

                if fields.peek().is_some() {
                    token_stream.extend(quote! { & });
                }
            }
            token_stream
        },
        #[expect(clippy::unimplemented, reason = "Not applicable to unit-like structs")]
        Fields::Unit => unimplemented!(),
    };

    let name = &input.ident;
    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
    let expanded = quote! {
        impl #impl_generics ::libthreema_dependencies::crypto::subtle::ConstantTimeEq
            for #name #type_generics #where_clause
        {
            #[inline]
            fn ct_eq(&self, other: &Self) -> ::libthreema_dependencies::crypto::subtle::Choice {
                use ::libthreema_dependencies::crypto::subtle::ConstantTimeEq as _;
                return #constant_time_eq_stream
            }
        }

        impl #impl_generics PartialEq<Self> for #name #type_generics #where_clause {
            #[inline]
            fn eq(&self, other: &Self) -> bool{
                use ::libthreema_dependencies::crypto::subtle::ConstantTimeEq as _;
                bool::from(self.ct_eq(other))
            }
        }
        impl #impl_generics Eq for #name #type_generics #where_clause {}
    };

    TokenStream::from(expanded)
}

// Avoids dependencies to be picked up by the linter that are used in tests.
mod external_crate_false_positives {
    use libthreema_dependencies as _;
}

// Avoids dependencies to be picked up by the linter that are used in tests.
#[cfg(test)]
mod external_crate_false_positives_test_feature {
    use rstest as _;
    use trybuild as _;
}

Messung
CHZG
939710096

¤ Dauer der Verarbeitung: 0.9 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.