svd_codegen 0.4.0

Generate Rust register maps (`struct`s) from SVD files
Documentation
#![recursion_limit = "100"]

extern crate inflections;
extern crate svd_parser as svd;
#[macro_use]
extern crate quote;
extern crate syn;

use std::io;
use std::io::Write;

use quote::Tokens;
use syn::*;

use inflections::Inflect;
use svd::{Access, Defaults, Peripheral, Register};

pub fn gen_peripheral(p: &mut Peripheral, d: &Defaults) -> Vec<Tokens> {
    if let Some(ref derived_from) = p.derived_from {
        let p_name = Ident::new(p.name.to_pascal_case());
        let derived_name = format!("::{}::{}",
                                   derived_from.to_snake_case(),
                                   derived_from.to_pascal_case());
        let derived_ty = Ident::new(derived_name);

        let type_alias = quote! {
            pub type #p_name = #derived_ty;
        };
        return vec![type_alias];
    }

    let mut items = vec![];
    let mut fields = vec![];
    let mut offset = 0;
    let mut i = 0;
    if let Some(ref mut registers) = p.registers {
        registers.sort_by_key(|r| r.address_offset);
    };
    let registers = p.registers
        .as_ref()
        .expect(&format!("{:#?} has no `registers` field", p));
    for register in registers {
        let pad = if let Some(pad) = register.address_offset
            .checked_sub(offset) {
            pad
        } else {
            writeln!(io::stderr(),
                     "WARNING {} overlaps with another register at offset \
                      {}. Ignoring.",
                     register.name,
                     register.address_offset)
                .ok();
            continue;
        };

        if pad != 0 {
            let name = Ident::new(format!("_reserved{}", i));
            let pad = pad as usize;
            fields.push(quote! {
                #name : [u8; #pad]
            });
            i += 1;
        }

        let comment = &format!("0x{:02x} - {}",
                               register.address_offset,
                               respace(&register.description))[..];

        let field_ty = Ident::new(register.name.to_pascal_case());
        let field_name = Ident::new(register.name.to_snake_case());
        let field_access = register.access.unwrap_or_else(|| {
            let fields = register.fields
                .as_ref()
                .expect(&format!("{:#?} has no `fields` field", register));
            if fields.iter().all(|f| f.access == Some(Access::ReadOnly)) {
                Access::ReadOnly
            } else if fields.iter().all(|f| f.access == Some(Access::WriteOnly)) {
                Access::WriteOnly
            } else if fields.iter().any(|f| f.access == Some(Access::ReadWrite)) {
                Access::ReadWrite
            } else {
                panic!("unexpected case: {:#?}",
                       fields.iter().map(|f| f.access).collect::<Vec<_>>())
            }
        });
        let field_access_ty = match field_access {
            Access::ReadOnly => Ident::new("volatile::ReadOnly"),
            Access::WriteOnly => Ident::new("volatile::WriteOnly"),
            Access::ReadWrite => Ident::new("volatile::ReadWrite"),
            a => panic!("{:?} registers are not supported", a),
        };

        fields.push(quote! {
            #[doc = #comment]
            pub #field_name : #field_access_ty<#field_ty>
        });

        offset = register.address_offset +
                 register.size
            .or(d.size)
            .expect(&format!("{:#?} has no `size` field", register)) / 8;
    }

    let p_name = Ident::new(p.name.to_pascal_case());

    if let Some(description) = p.description.as_ref() {
        let comment = &respace(description)[..];
        items.push(quote! {
            #[doc = #comment]
        });
    }

    let struct_ = quote! {
        #[repr(C)]
        pub struct #p_name {
            #(#fields),*
        }
    };

    items.push(struct_);

    for register in registers {
        items.extend(gen_register(register, d));
        items.extend(gen_register_read_methods(register, d));
        items.extend(gen_register_default_impl(register, d));
        items.extend(gen_register_write_methods(register, d));
    }

    items
}

pub fn gen_register(r: &Register, d: &Defaults) -> Vec<Tokens> {
    let mut items = vec![];

    let name = Ident::new(r.name.to_pascal_case());
    let bits_ty = r.size
        .or(d.size)
        .expect(&format!("{:#?} has no `size` field", r))
        .to_ty();

    items.push(quote! {
        #[derive(Debug, Clone, Copy)]
        #[repr(C)]
        pub struct #name {
            bits: #bits_ty
        }
    });

    items
}

pub fn gen_register_read_methods(r: &Register, _d: &Defaults) -> Vec<Tokens> {
    let mut items = vec![];

    let name = Ident::new(r.name.to_pascal_case());

    let mut impl_items = vec![];

    if r.fields.is_some() {
        for field in r.fields
            .as_ref()
            .expect(&format!("{:#?} has no `fields` field", r)) {
            if let Some(Access::WriteOnly) = field.access {
                continue;
            }

            let mut field_name = field.name.to_snake_case();
            if field_name.as_str() == "match" {
                field_name += "_";
            }
            let field_name = Ident::new(field_name);
            let offset = field.bit_range.offset as u8;

            let width = field.bit_range.width as u8;

            if let Some(description) = field.description.as_ref() {
                let bits = if width == 1 {
                    format!("Bit {}", offset)
                } else {
                    format!("Bits {}:{}", offset, offset + width - 1)
                };

                let comment = &format!("{} - {}", bits, respace(description))[..];
                impl_items.push(quote! {
                #[doc = #comment]
            });
            }

            let item = if width == 1 {
                quote! {
                    pub fn #field_name(&self) -> bool {
                        self.bits.get_bit(#offset)
                    }
                }
            } else {
                let start = offset;
                let end = offset + width;
                let width_ty = field.bit_range.width.to_ty();

                quote! {
                pub fn #field_name(&self) -> #width_ty {
                    self.bits.get_range(#start..#end) as #width_ty
                }
            }
            };

            impl_items.push(item);
        }
    }

    items.push(quote! {
        impl #name {
            #(#impl_items)*
        }
    });

    items
}

pub fn gen_register_default_impl(r: &Register, d: &Defaults) -> Vec<Tokens> {
    let mut items = vec![];

    let name = Ident::new(format!("{}", r.name.to_pascal_case()));

    if let Some(reset_value) = r.reset_value.or(d.reset_value) {
        items.push(quote! {
            impl Default for #name {
                /// Reset value
                fn default() -> Self {
                    #name { bits: #reset_value }
                }
            }
        });
    }

    items
}

pub fn gen_register_write_methods(r: &Register, d: &Defaults) -> Vec<Tokens> {
    let mut items = vec![];

    let name = Ident::new(format!("{}", r.name.to_pascal_case()));
    let bits_ty = r.size
        .or(d.size)
        .expect(&format!("{:#?} has no `size` field", r))
        .to_ty();

    let mut impl_items = vec![];

    if r.fields.is_some() {
        for field in r.fields
            .as_ref()
            .expect(&format!("{:#?} has no `fields` field", r)) {
            if let Some(Access::ReadOnly) = field.access {
                continue;
            }

            let name = Ident::new(format!("set_{}", field.name.to_snake_case()));
            let offset = field.bit_range.offset as u8;

            let width = field.bit_range.width as u8;

            if let Some(description) = field.description.as_ref() {
                let bits = if width == 1 {
                    format!("Bit {}", offset)
                } else {
                    format!("Bits {}:{}", offset, offset + width - 1)
                };

                let comment = &format!("{} - {}", bits, respace(description))[..];
                impl_items.push(quote! {
                #[doc = #comment]
            });
            }

            let item = if width == 1 {
                quote! {
                pub fn #name(&mut self, value: bool) {
                    self.bits.set_bit(#offset, value);
                }
            }
            } else {
                let start = offset;
                let end = offset + width;
                let width_ty = field.bit_range.width.to_ty();

                quote! {
                pub fn #name(&mut self, value: #width_ty) {
                    self.bits.set_range(#start..#end, value as #bits_ty);
                }
            }
            };

            impl_items.push(item);
        }
    }

    items.push(quote! {
        impl #name {
            #(#impl_items)*
        }
    });

    items
}

trait U32Ext {
    fn to_ty(&self) -> Ident;
}

impl U32Ext for u32 {
    fn to_ty(&self) -> Ident {
        match *self {
            1...8 => Ident::new("u8"),
            9...16 => Ident::new("u16"),
            17...32 => Ident::new("u32"),
            _ => panic!("{}.to_ty()", *self),
        }
    }
}

fn respace(s: &str) -> String {
    s.split_whitespace().collect::<Vec<_>>().join(" ")
}