use std::env; use std::fs; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::path::Path; use phf_codegen::Map; use quote::quote; /* This build script contains a "parser" for the PCI ID database. * "Parser" is in scare-quotes because it's really a line matcher with a small amount * of context needed for pairing nested entities (e.g. devices) with their parents (e.g. vendors). */ struct CgVendor { id: u16, name: String, devices: Vec, } struct CgDevice { id: u16, name: String, subsystems: Vec, } struct CgSubSystem { subvendor: u16, subdevice: u16, name: String, } struct CgClass { id: u8, name: String, subclasses: Vec, } struct CgSubclass { id: u8, name: String, prog_ifs: Vec, } pub struct CgProgIf { id: u8, name: String, } #[allow(clippy::redundant_field_names)] fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); let src_path = Path::new("pciids/pci.ids"); let dest_path = Path::new(&out_dir).join("pci_ids.cg.rs"); let input = { let f = fs::File::open(src_path).unwrap(); BufReader::new(f) }; let mut output = { let f = fs::File::create(dest_path).unwrap(); BufWriter::new(f) }; // Parser state. let mut curr_vendor: Option = None; let mut curr_device_id = 0u16; let mut curr_class: Option = None; let mut curr_subclass_id = 0u8; let mut vendors = Map::new(); let mut classes = Map::new(); for line in input.lines() { let line = line.unwrap(); if line.is_empty() || line.starts_with('#') { continue; } if let Ok((name, id)) = parser::vendor(&line) { // If there was a previous vendor, emit it. if let Some(vendor) = curr_vendor.take() { vendors.entry(vendor.id, "e!(#vendor).to_string()); } // Set our new vendor as the current vendor. curr_vendor = Some(CgVendor { id, name: name.into(), devices: vec![], }); } else if let Ok((name, id)) = parser::device(&line) { // We should always have a current vendor; failure here indicates a malformed input. let curr_vendor = curr_vendor.as_mut().unwrap(); curr_vendor.devices.push(CgDevice { id, name: name.into(), subsystems: vec![], }); curr_device_id = id; } else if let Ok((name, (subvendor, subdevice))) = parser::subsystems(&line) { // We should always have a current vendor; failure here indicates a malformed input. // Similarly, our current vendor should always have a device corresponding // to the current device id. let curr_vendor = curr_vendor.as_mut().unwrap(); let curr_device = curr_vendor .devices .iter_mut() .find(|d| d.id == curr_device_id) .unwrap(); curr_device.subsystems.push(CgSubSystem { subvendor, subdevice, name: name.into(), }); } else if let Ok((name, id)) = parser::class(&line) { // If there was a previous class, emit it. if let Some(class) = curr_class.take() { classes.entry(class.id, "e!(#class).to_string()); } // Set our new class as the current class. curr_class = Some(CgClass { id, name: name.into(), subclasses: vec![], }); } else if let Ok((name, id)) = parser::subclass(&line) { // We should always have a current class; failure here indicates a malformed input. let curr_class = curr_class.as_mut().unwrap(); curr_class.subclasses.push(CgSubclass { id, name: name.into(), prog_ifs: vec![], }); curr_subclass_id = id; } else if let Ok((name, id)) = parser::prog_if(&line) { // We should always have a current class; failure here indicates a malformed input. // Similarly, our current class should always have a subclass corresponding // to the current subclass id. let curr_class = curr_class.as_mut().unwrap(); let curr_subclass = curr_class .subclasses .iter_mut() .find(|d| d.id == curr_subclass_id) .unwrap(); curr_subclass.prog_ifs.push(CgProgIf { id, name: name.into(), }); } else { // TODO: Lots of other things that could be parsed out: // Language, dialect, country code, HID types, ... break; } } if let Some(vendor) = curr_vendor.take() { vendors.entry(vendor.id, "e!(#vendor).to_string()); } if let Some(class) = curr_class.take() { classes.entry(class.id, "e!(#class).to_string()); } writeln!( output, "static VENDORS: phf::Map = {};", vendors.build() ) .unwrap(); writeln!( output, "static CLASSES: phf::Map = {};", classes.build() ) .unwrap(); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=pciids/pci.ids"); } mod parser { use std::num::ParseIntError; use nom::bytes::complete::{tag, take}; use nom::character::complete::{hex_digit1, tab}; use nom::combinator::{all_consuming, map_parser, map_res}; use nom::sequence::{delimited, separated_pair, terminated}; use nom::IResult; fn id(size: usize, from_str_radix: F) -> impl Fn(&str) -> IResult<&str, T> where F: Fn(&str, u32) -> Result, { move |input| { map_res(map_parser(take(size), all_consuming(hex_digit1)), |input| { from_str_radix(input, 16) })(input) } } pub fn vendor(input: &str) -> IResult<&str, u16> { let id = id(4, u16::from_str_radix); terminated(id, tag(" "))(input) } pub fn device(input: &str) -> IResult<&str, u16> { let id = id(4, u16::from_str_radix); delimited(tab, id, tag(" "))(input) } pub fn subsystems(input: &str) -> IResult<&str, (u16, u16)> { let subvendor = id(4, u16::from_str_radix); let subdevice = id(4, u16::from_str_radix); let id = separated_pair(subvendor, tag(" "), subdevice); delimited(tag("\t\t"), id, tag(" "))(input) } pub fn class(input: &str) -> IResult<&str, u8> { let id = id(2, u8::from_str_radix); delimited(tag("C "), id, tag(" "))(input) } pub fn subclass(input: &str) -> IResult<&str, u8> { let id = id(2, u8::from_str_radix); delimited(tab, id, tag(" "))(input) } pub fn prog_if(input: &str) -> IResult<&str, u8> { let id = id(2, u8::from_str_radix); delimited(tag("\t\t"), id, tag(" "))(input) } } impl quote::ToTokens for CgVendor { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let CgVendor { id: vendor_id, name, devices, } = self; let devices = devices.iter().map(|CgDevice { id, name, subsystems }| { quote! { Device { vendor_id: #vendor_id, id: #id, name: #name, subsystems: &[#(#subsystems),*] } } }); tokens.extend(quote! { Vendor { id: #vendor_id, name: #name, devices: &[#(#devices),*] } }); } } impl quote::ToTokens for CgSubSystem { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let CgSubSystem { subvendor, subdevice, name, } = self; tokens.extend(quote! { SubSystem { subvendor: #subvendor, subdevice: #subdevice, name: #name } }); } } impl quote::ToTokens for CgClass { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let CgClass { id: class_id, name, subclasses, } = self; let subclasses = subclasses.iter().map(|CgSubclass { id, name, prog_ifs }| { quote! { Subclass { class_id: #class_id, id: #id, name: #name, prog_ifs: &[#(#prog_ifs),*] } } }); tokens.extend(quote! { Class { id: #class_id, name: #name, subclasses: &[#(#subclasses),*] } }) } } impl quote::ToTokens for CgProgIf { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let CgProgIf { id, name } = self; tokens.extend(quote! { ProgIf { id: #id, name: #name } }); } }