tracelogging_macros 0.1.0

TraceLogging for Rust Proc Macros
Documentation
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

//! Tool that generates a markdown table describing the normal field types supported by
//! the [`write_event`] macro. This table is included in the documentation for
//! `write_event` in `tracelogging/src/lib.rs`.

#![allow(clippy::needless_return)]

use std::cmp::Ordering;

#[allow(dead_code)]
#[path = "../src/enums.rs"]
mod enums;

#[allow(dead_code)]
#[path = "../src/field_option.rs"]
mod field_option;

#[path = "../src/field_options.rs"]
mod field_options;

#[allow(dead_code)]
#[path = "../src/strings.rs"]
mod strings;

use enums::*;
use field_option::*;
use field_options::FIELD_OPTIONS;

fn main() {
    println!("/// | Field Type | Rust Type | ETW Type");
    println!("/// |------------|-----------|---------");

    let mut options = Vec::from_iter(FIELD_OPTIONS);
    options.sort_by(|a, b| option_name_cmp(a.option_name, b.option_name));
    for field in options {
        let s = field.to_markdown();
        if !s.is_empty() {
            println!("{}", &s);
        }
    }
}

/// option_name_cmp tries to be human-friendly:
/// - `foo_slice` sorts immediately after `foo`.
/// - `foo_` sorts immediately after `foo_slice` ("str" < "str_xml" < "str16").
/// - Numbers are sorted by value ("u8" < "u16").
fn option_name_cmp(val1: &str, val2: &str) -> Ordering {
    let mut v1 = val1.as_bytes();
    let mut v2 = val2.as_bytes();
    let mut pos1 = 0;
    let mut pos2 = 0;

    const SLICE: &[u8] = b"_slice";

    let slice1 = v1.ends_with(SLICE);
    if slice1 {
        v1 = &v1[..v1.len() - SLICE.len()];
    }

    let slice2 = v2.ends_with(SLICE);
    if slice2 {
        v2 = &v2[..v2.len() - SLICE.len()];
    }

    let len1 = v1.len();
    let len2 = v2.len();

    let cmp = loop {
        if pos1 == len1 {
            if pos2 == len2 {
                break slice1.cmp(&slice2);
            } else {
                break Ordering::Less;
            }
        } else if pos2 == len2 {
            break Ordering::Greater;
        }

        let ch1 = v1[pos1];
        let ch2 = v2[pos2];

        if ch1.is_ascii_digit() && ch2.is_ascii_digit() {
            let num1 = parse_int(v1, pos1);
            let num2 = parse_int(v2, pos2);

            let num_cmp = num1.value.cmp(&num2.value);
            if num_cmp != Ordering::Equal {
                break num_cmp;
            } else {
                pos1 = num1.end_pos;
                pos2 = num2.end_pos;
            }
        } else {
            let char_cmp = ch1.cmp(&ch2);
            if char_cmp != Ordering::Equal {
                if ch1 == b'_' {
                    break Ordering::Less;
                } else if ch2 == b'_' {
                    break Ordering::Greater;
                } else {
                    break char_cmp;
                }
            }

            pos1 += 1;
            pos2 += 1;
        }
    };

    return cmp;
}

trait ToMarkdown {
    fn to_markdown(&self) -> String;
    fn normal_field(&self, s: &mut String, type_path: &[&str], is_slice: bool, note: &str);
}

impl ToMarkdown for FieldOption {
    /// Produces a markdown table row that describes this field type, or "" if field type is special.
    ///
    /// `| Field Type | Rust Type | ETW Type`
    fn to_markdown(&self) -> String {
        let mut s = String::new();

        match self.strategy {
            FieldStrategy::Scalar => {
                self.normal_field(&mut s, self.value_type, false, "");
            }
            FieldStrategy::SystemTime => {
                self.normal_field(&mut s, &["std", "time", "SystemTime"], false, "systemtime");
            }
            FieldStrategy::Sid => {
                self.normal_field(&mut s, self.value_type, true, "sid");
            }
            FieldStrategy::CStr => {
                self.normal_field(&mut s, self.value_type, true, "cstr");
            }
            FieldStrategy::Counted => {
                let note = if matches!(self.intype, InType::BinaryC) {
                    "binaryc"
                } else {
                    ""
                };
                self.normal_field(&mut s, self.value_type, self.value_array_count == 0, note);
            }
            FieldStrategy::Slice => {
                self.normal_field(&mut s, self.value_type, true, "");
            }
            FieldStrategy::Struct
            | FieldStrategy::RawStruct
            | FieldStrategy::RawStructSlice
            | FieldStrategy::RawData
            | FieldStrategy::RawField
            | FieldStrategy::RawFieldSlice
            | FieldStrategy::RawMeta
            | FieldStrategy::RawMetaSlice => {}
        }

        return s;
    }

    fn normal_field(&self, s: &mut String, type_path: &[&str], is_slice: bool, note: &str) {
        use std::fmt::Write;

        s.push_str("/// | ");

        s.push('`');
        s.push_str(self.option_name);
        s.push('`');
        if !note.is_empty() {
            s.push_str(" [^");
            s.push_str(note);
            s.push(']');
        }

        s.push_str(" | `&");

        if is_slice {
            s.push('[');
        }

        if self.value_array_count != 0 {
            s.push('[');
        }

        let type_path_start = if type_path[0] == "core" { 2 } else { 0 };
        s.push_str(type_path[type_path_start]);
        for type_path_part in type_path.iter().skip(type_path_start + 1) {
            s.push_str("::");
            s.push_str(type_path_part);
        }

        if self.value_array_count != 0 {
            write!(s, "; {}]", self.value_array_count).unwrap();
        }

        if is_slice {
            s.push(']');
        }

        s.push_str("` | ");

        push_enum_value(s, "InType", intype_to_string(self.intype));
        if !matches!(self.outtype, OutType::Default) {
            s.push_str(" + ");
            push_enum_value(s, "OutType", outtype_to_string(self.outtype));
        }
    }
}

struct ParseResult {
    end_pos: usize,
    value: u32,
}

fn push_enum_value(s: &mut String, enum_name: &str, enum_value: &str) {
    s.push_str("[`");
    s.push_str(enum_value);
    s.push_str("`](");
    s.push_str(enum_name);
    s.push_str("::");
    s.push_str(enum_value);
    s.push(')');
}

fn parse_int(str: &[u8], pos: usize) -> ParseResult {
    let mut end_pos = pos + 1;
    let mut value = (str[pos] - b'0') as u32;
    while end_pos != str.len() && str[end_pos].is_ascii_digit() {
        value = value * 10 + (str[end_pos] - b'0') as u32;
        end_pos += 1;
    }

    return ParseResult { end_pos, value };
}

fn intype_to_string(value: InType) -> &'static str {
    return match value {
        InType::Invalid => "Invalid",
        InType::CStr16 => "CStr16",
        InType::CStr8 => "CStr8",
        InType::I8 => "I8",
        InType::U8 => "U8",
        InType::I16 => "I16",
        InType::U16 => "U16",
        InType::I32 => "I32",
        InType::U32 => "U32",
        InType::I64 => "I64",
        InType::U64 => "U64",
        InType::F32 => "F32",
        InType::F64 => "F64",
        InType::Bool32 => "Bool32",
        InType::Binary => "Binary",
        InType::Guid => "Guid",
        InType::_HexSizePlatformSpecific => "HexSizePlatformSpecific",
        InType::FileTime => "FileTime",
        InType::SystemTime => "SystemTime",
        InType::Sid => "Sid",
        InType::Hex32 => "Hex32",
        InType::Hex64 => "Hex64",
        InType::Str16 => "Str16",
        InType::Str8 => "Str8",
        InType::Struct => "Struct",
        InType::BinaryC => "BinaryC",
        InType::ISize => "ISize",
        InType::USize => "USize",
        InType::HexSize => "HexSize",
    };
}

fn outtype_to_string(value: OutType) -> &'static str {
    return match value {
        OutType::Default => "Default",
        OutType::_NoPrint => "NoPrint",
        OutType::String => "String",
        OutType::Boolean => "Boolean",
        OutType::Hex => "Hex",
        OutType::Pid => "Pid",
        OutType::Tid => "Tid",
        OutType::Port => "Port",
        OutType::IPv4 => "IPv4",
        OutType::IPv6 => "IPv6",
        OutType::SocketAddress => "SocketAddress",
        OutType::Xml => "Xml",
        OutType::Json => "Json",
        OutType::Win32Error => "Win32Error",
        OutType::NtStatus => "NtStatus",
        OutType::HResult => "HResult",
        OutType::_DateTime => "DateTime",
        OutType::_Signed => "Signed",
        OutType::_Unsigned => "Unsigned",
        OutType::_DateTimeCultureInsensitive => "DateTimeCultureInsensitive",
        OutType::Utf8 => "Utf8",
        OutType::_Pkcs7WithTypeInfo => "Pkcs7WithTypeInfo",
        OutType::CodePointer => "CodePointer",
        OutType::DateTimeUtc => "DateTimeUtc",
    };
}