#![recursion_limit = "128"]
extern crate either;
extern crate inflections;
extern crate svd_parser as svd;
#[macro_use]
extern crate quote;
extern crate syn;
use std::borrow::Cow;
use std::collections::HashSet;
use std::io::Write;
use std::io;
use std::rc::Rc;
use either::Either;
use inflections::Inflect;
use quote::Tokens;
use svd::{Access, BitRange, Defaults, EnumeratedValues, Field, Peripheral,
Register, RegisterInfo, Usage};
use syn::*;
const BLACKLIST_CHARS: &'static [char] = &['(', ')'];
trait ToSanitizedPascalCase {
fn to_sanitized_pascal_case(&self) -> Cow<str>;
}
trait ToSanitizedSnakeCase {
fn to_sanitized_snake_case(&self) -> Cow<str>;
}
impl ToSanitizedSnakeCase for str {
fn to_sanitized_snake_case(&self) -> Cow<str> {
macro_rules! keywords {
($s:expr, $($kw:ident),+,) => {
Cow::from(match &$s.to_lowercase()[..] {
$(stringify!($kw) => concat!(stringify!($kw), "_")),+,
_ => return Cow::from($s.to_snake_case())
})
}
}
let s = self.replace(BLACKLIST_CHARS, "");
match s.chars().next().unwrap_or('\0') {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
Cow::from(format!("_{}", s.to_snake_case()))
}
_ => {
keywords! {
s,
abstract,
alignof,
as,
become,
box,
break,
const,
continue,
crate,
do,
else,
enum,
extern,
false,
final,
fn,
for,
if,
impl,
in,
let,
loop,
macro,
match,
mod,
move,
mut,
offsetof,
override,
priv,
proc,
pub,
pure,
ref,
return,
self,
sizeof,
static,
struct,
super,
trait,
true,
type,
typeof,
unsafe,
unsized,
use,
virtual,
where,
while,
yield,
}
}
}
}
}
impl ToSanitizedPascalCase for str {
fn to_sanitized_pascal_case(&self) -> Cow<str> {
let s = self.replace(BLACKLIST_CHARS, "");
match s.chars().next().unwrap_or('\0') {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => {
Cow::from(format!("_{}", s.to_pascal_case()))
}
_ => Cow::from(s.to_pascal_case()),
}
}
}
#[doc(hidden)]
pub fn gen_peripheral(p: &Peripheral, d: &Defaults) -> Vec<Tokens> {
assert!(p.derived_from.is_none(),
"DerivedFrom not supported here (should be resolved earlier)");
let mut items = vec![];
let mut fields = vec![];
let mut offset = 0;
let mut i = 0;
let registers = p.registers
.as_ref()
.expect(&format!("{:#?} has no `registers` field", p));
for register in &expand(registers) {
let pad = if let Some(pad) = register.offset
.checked_sub(offset) {
pad
} else {
writeln!(io::stderr(),
"WARNING {} overlaps with another register at offset \
{}. Ignoring.",
register.name,
register.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.offset,
respace(®ister.info
.description))
[..];
let reg_ty = match register.ty {
Either::Left(ref ty) => Ident::from(&**ty),
Either::Right(ref ty) => Ident::from(&***ty),
};
let reg_name = Ident::new(&*register.name.to_sanitized_snake_case());
fields.push(quote! {
#[doc = #comment]
pub #reg_name : #reg_ty
});
offset = register.offset +
register.info
.size
.or(d.size)
.expect(&format!("{:#?} has no `size` field", register.info)) /
8;
}
let p_name = Ident::new(&*p.name.to_sanitized_pascal_case());
let doc = p.description
.as_ref()
.map(|s| respace(s))
.unwrap_or_else(|| "Peripheral".to_owned());
items.push(quote! {
#![doc = #doc]
#[repr(C)]
pub struct #p_name {
#(#fields),*
}
});
for register in registers {
items.extend(gen_register(register, d, registers));
}
items
}
struct ExpandedRegister<'a> {
info: &'a RegisterInfo,
name: String,
offset: u32,
ty: Either<String, Rc<String>>,
}
fn expand(registers: &[Register]) -> Vec<ExpandedRegister> {
let mut out = vec![];
for r in registers {
match *r {
Register::Single(ref info) => {
out.push(ExpandedRegister {
info: info,
name: info.name.to_sanitized_snake_case().into_owned(),
offset: info.address_offset,
ty: Either::Left(info.name
.to_sanitized_pascal_case()
.into_owned()),
})
}
Register::Array(ref info, ref array_info) => {
let has_brackets = info.name.contains("[%s]");
let ty = if has_brackets {
info.name.replace("[%s]", "")
} else {
info.name.replace("%s", "")
};
let ty = Rc::new(ty.to_sanitized_pascal_case().into_owned());
let indices = array_info.dim_index
.as_ref()
.map(|v| Cow::from(&**v))
.unwrap_or_else(|| {
Cow::from((0..array_info.dim)
.map(|i| i.to_string())
.collect::<Vec<_>>())
});
for (idx, i) in indices.iter().zip(0..) {
let name = if has_brackets {
info.name.replace("[%s]", idx)
} else {
info.name.replace("%s", idx)
};
let offset = info.address_offset +
i * array_info.dim_increment;
out.push(ExpandedRegister {
info: info,
name: name.to_sanitized_snake_case().into_owned(),
offset: offset,
ty: Either::Right(ty.clone()),
});
}
}
}
}
out.sort_by_key(|x| x.offset);
out
}
fn name_of(r: &Register) -> Cow<str> {
match *r {
Register::Single(ref info) => Cow::from(&*info.name),
Register::Array(ref info, _) => {
if info.name.contains("[%s]") {
info.name.replace("[%s]", "").into()
} else {
info.name.replace("%s", "").into()
}
}
}
}
fn access(r: &Register) -> Access {
r.access.unwrap_or_else(|| if let Some(ref fields) = r.fields {
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 {
Access::ReadWrite
}
} else {
Access::ReadWrite
})
}
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
#[doc(hidden)]
pub fn gen_register(r: &Register,
d: &Defaults,
all_registers: &[Register])
-> Vec<Tokens> {
let mut items = vec![];
let name = name_of(r);
let name_pc = Ident::new(&*name.to_sanitized_pascal_case());
let name_sc = Ident::new(&*name.to_sanitized_snake_case());
let reg_ty = r.size
.or(d.size)
.expect(&format!("{:#?} has no `size` field", r))
.to_ty();
let access = access(r);
let doc = respace(&r.description);
match access {
Access::ReadOnly => {
items.push(quote! {
#[doc = #doc]
#[repr(C)]
pub struct #name_pc {
register: ::volatile_register::RO<#reg_ty>
}
});
}
Access::ReadWrite => {
items.push(quote! {
#[doc = #doc]
#[repr(C)]
pub struct #name_pc {
register: ::volatile_register::RW<#reg_ty>
}
});
}
Access::WriteOnly => {
items.push(quote! {
#[doc = #doc]
#[repr(C)]
pub struct #name_pc {
register: ::volatile_register::WO<#reg_ty>
}
});
}
_ => unreachable!(),
}
let mut mod_items = vec![];
let mut impl_items = vec![];
let mut r_impl_items = vec![];
let mut w_impl_items = vec![];
if access == Access::ReadWrite {
impl_items.push(quote! {
pub fn modify<F>(&mut self, f: F)
where for<'w> F: FnOnce(&R, &'w mut W) -> &'w mut W,
{
let bits = self.register.read();
let r = R { bits: bits };
let mut w = W { bits: bits };
f(&r, &mut w);
self.register.write(w.bits);
}
});
}
if access == Access::ReadOnly || access == Access::ReadWrite {
impl_items.push(quote! {
pub fn read(&self) -> R {
R { bits: self.register.read() }
}
});
mod_items.push(quote! {
pub struct R {
bits: #reg_ty,
}
});
r_impl_items.push(quote! {
pub fn bits(&self) -> #reg_ty {
self.bits
}
});
}
if access == Access::WriteOnly || access == Access::ReadWrite {
impl_items.push(quote! {
pub fn write<F>(&mut self, f: F)
where F: FnOnce(&mut W) -> &mut W,
{
let mut w = W::reset_value();
f(&mut w);
self.register.write(w.bits);
}
});
mod_items.push(quote! {
pub struct W {
bits: #reg_ty,
}
});
if let Some(reset_value) =
r.reset_value
.or(d.reset_value)
.map(|x| Lit::Int(x as u64, IntTy::Unsuffixed)) {
w_impl_items.push(quote! {
pub fn reset_value() -> W {
W { bits: #reset_value }
}
});
}
w_impl_items.push(quote! {
pub unsafe fn bits(&mut self, bits: #reg_ty) -> &mut Self {
self.bits = bits;
self
}
});
}
mod_items.push(quote! {
impl super::#name_pc {
#(#impl_items)*
}
});
let fields = r.fields.as_ref().map(|fs| &**fs).unwrap_or(&[]);
if !fields.is_empty() {
let mut reexported = HashSet::new();
if access == Access::ReadOnly || access == Access::ReadWrite {
for field in fields {
if field.access == Some(Access::WriteOnly) {
continue;
}
let field_name = Ident::new(&*field.name
.to_sanitized_snake_case());
let _field_name =
Ident::new(&*format!("_{}",
field.name
.replace(BLACKLIST_CHARS, "")
.to_snake_case()));
let width = field.bit_range.width;
let mask = Lit::Int((1u64 << width) - 1, IntTy::Unsuffixed);
let offset = Lit::Int(u64::from(field.bit_range.offset),
IntTy::Unsuffixed);
let field_ty = width.to_ty();
r_impl_items.push(quote! {
fn #_field_name(&self) -> #field_ty {
const MASK: #field_ty = #mask;
const OFFSET: u8 = #offset;
((self.bits >> OFFSET) & MASK as #reg_ty) as #field_ty
}
});
if let Some((evs, base)) =
lookup(&field.enumerated_values,
fields,
all_registers,
Usage::Read) {
struct Variant {
doc: Cow<'static, str>,
pc: Ident,
sc: Option<Ident>,
value: u64,
}
let variants = (0..1 << width)
.map(|i| {
let value = u64::from(i);
if let Some(ev) = evs.values
.iter()
.find(|ev| ev.value == Some(i)) {
let sc = Ident::new(&*ev.name
.replace(BLACKLIST_CHARS, "")
.to_snake_case());
let doc = Cow::from(ev.description
.clone()
.unwrap_or_else(|| {
format!("A possible value of \
the field `{}`",
sc)
}));
Variant {
doc: doc,
pc: Ident::new(&*ev.name
.to_sanitized_pascal_case()),
sc: Some(sc),
value: value,
}
} else {
Variant {
doc: Cow::from("Reserved"),
pc: Ident::new(format!("_Reserved{:b}", i)),
sc: None,
value: value,
}
}
})
.collect::<Vec<_>>();
let variants_pc = variants.iter().map(|v| &v.pc);
let enum_name = if let Some(ref base) = base {
Ident::new(&*format!("{}R",
base.field
.to_sanitized_pascal_case()))
} else {
Ident::new(&*format!("{}R",
evs.name
.as_ref()
.unwrap_or(&field.name)
.to_sanitized_pascal_case()))
};
if let Some(register) = base.as_ref()
.and_then(|base| base.register) {
let register =
Ident::new(&*register.to_sanitized_snake_case());
if !reexported.contains(&enum_name) {
mod_items.push(quote! {
pub use super::#register::#enum_name;
});
reexported.insert(enum_name.clone());
}
}
let doc = field_doc(field.bit_range,
field.description.as_ref());
r_impl_items.push(quote! {
#[doc = #doc]
pub fn #field_name(&self) -> #enum_name {
#enum_name::_from(self.#_field_name())
}
});
if base.is_none() {
let doc = format!("Possible values of the field `{}`",
field_name);
let variants_doc = variants.iter().map(|v| &*v.doc);
mod_items.push(quote! {
#[doc = #doc]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum #enum_name {
#(#[doc = #variants_doc]
#variants_pc),*
}
});
let mut enum_items = vec![];
let arms = variants.iter()
.map(|v| {
let value = Lit::Int(v.value,
IntTy::Unsuffixed);
let pc = &v.pc;
quote! {
#enum_name::#pc => #value
}
});
enum_items.push(quote! {
pub fn bits(&self) -> #field_ty {
match *self {
#(#arms),*
}
}
});
let arms = variants.iter()
.map(|v| {
let i = Lit::Int(v.value, IntTy::Unsuffixed);
let pc = &v.pc;
quote! {
#i => #enum_name::#pc
}
});
enum_items.push(quote! {
#[allow(missing_docs)]
#[doc(hidden)]
#[inline(always)]
pub fn _from(bits: #field_ty) -> #enum_name {
match bits {
#(#arms),*,
_ => unreachable!(),
}
}
});
for v in &variants {
if let Some(ref sc) = v.sc {
let pc = &v.pc;
let is_variant = {
Ident::new(&*format!("is_{}", sc))
};
let doc = format!("Check if \
the value of the field \
is `{}`",
pc);
enum_items.push(quote! {
#[doc = #doc]
pub fn #is_variant(&self) -> bool {
*self == #enum_name::#pc
}
});
}
}
mod_items.push(quote! {
impl #enum_name {
#(#enum_items)*
}
});
}
} else {
let name = Ident::new(&*format!("{}R",
field.name
.to_sanitized_pascal_case()));
let doc = format!("Value of the field {}", field.name);
mod_items.push(quote! {
#[doc = #doc]
pub struct #name {
bits: #field_ty,
}
impl #name {
pub fn bits(&self) -> #field_ty {
self.bits
}
}
});
let doc = field_doc(field.bit_range,
field.description.as_ref());
r_impl_items.push(quote! {
#[doc = #doc]
pub fn #field_name(&self) -> #name {
#name { bits: self.#_field_name() }
}
});
}
}
}
if access == Access::WriteOnly || access == Access::ReadWrite {
for field in fields {
if field.access == Some(Access::ReadOnly) {
continue;
}
let field_name_sc = Ident::new(&*field.name
.to_sanitized_snake_case());
let width = field.bit_range.width;
let mask = Lit::Int((1u64 << width) - 1, IntTy::Unsuffixed);
let offset = Lit::Int(u64::from(field.bit_range.offset),
IntTy::Unsuffixed);
let field_ty = width.to_ty();
let proxy = Ident::new(&*format!("_{}W",
field.name
.to_pascal_case()));
mod_items.push(quote! {
pub struct #proxy<'a> {
register: &'a mut W,
}
});
let mut proxy_items = vec![];
let mut bits_is_safe = false;
if let Some((evs, base)) =
lookup(&field.enumerated_values,
fields,
all_registers,
Usage::Write) {
struct Variant {
doc: String,
pc: Ident,
sc: Ident,
value: u64,
}
let enum_name = if let Some(ref base) = base {
Ident::new(&*format!("{}W",
base.field
.to_sanitized_pascal_case()))
} else {
Ident::new(&*format!("{}W",
evs.name
.as_ref()
.unwrap_or(&field.name)
.to_sanitized_pascal_case()))
};
if let Some(register) = base.as_ref()
.and_then(|base| base.register) {
let register =
Ident::new(&*register.to_sanitized_snake_case());
if !reexported.contains(&enum_name) {
mod_items.push(quote! {
pub use super::#register::#enum_name;
});
reexported.insert(enum_name.clone());
}
}
let variants =
evs.values
.iter()
.map(|ev| {
let value = u64::from(ev.value
.expect("no value in EnumeratedValue"));
Variant {
doc: ev.description
.clone()
.unwrap_or_else(|| {
format!("`{:b}`", value)
}),
pc: Ident::new(&*ev.name
.to_sanitized_pascal_case()),
sc: Ident::new(&*ev.name
.to_sanitized_snake_case()),
value: value,
}
})
.collect::<Vec<_>>();
bits_is_safe = variants.len() == 1 << width;
if base.is_none() {
let variants_pc = variants.iter().map(|v| &v.pc);
let doc = {
format!("Values that can be written \
to the field `{}`",
field_name_sc)
};
let variants_doc = variants.iter().map(|v| &*v.doc);
mod_items.push(quote! {
#[doc = #doc]
pub enum #enum_name {
#(#[doc = #variants_doc]
#variants_pc),*
}
});
let arms = variants.iter()
.map(|v| {
let pc = &v.pc;
let value = Lit::Int(v.value,
IntTy::Unsuffixed);
quote! {
#enum_name::#pc => #value
}
});
mod_items.push(quote! {
impl #enum_name {
#[allow(missing_docs)]
#[doc(hidden)]
#[inline(always)]
pub fn _bits(&self) -> #field_ty {
match *self {
#(#arms),*
}
}
}
});
}
if bits_is_safe {
proxy_items.push(quote! {
pub fn variant(self,
variant: #enum_name) -> &'a mut W {
self.bits(variant._bits())
}
});
} else {
proxy_items.push(quote! {
pub fn variant(self,
variant: #enum_name) -> &'a mut W {
unsafe {
self.bits(variant._bits())
}
}
});
}
for v in &variants {
let pc = &v.pc;
let sc = &v.sc;
let doc = respace(&v.doc);
proxy_items.push(quote! {
#[doc = #doc]
pub fn #sc(self) -> &'a mut W {
self.variant(#enum_name::#pc)
}
});
}
}
if bits_is_safe {
proxy_items.push(quote! {
pub fn bits(self, bits: #field_ty) -> &'a mut W {
const MASK: #field_ty = #mask;
const OFFSET: u8 = #offset;
self.register.bits &=
!((MASK as #reg_ty) << OFFSET);
self.register.bits |=
((bits & MASK) as #reg_ty) << OFFSET;
self.register
}
});
} else {
proxy_items.push(quote! {
pub unsafe fn bits(self,
bits: #field_ty) -> &'a mut W {
const MASK: #field_ty = #mask;
const OFFSET: u8 = #offset;
self.register.bits &=
!((MASK as #reg_ty) << OFFSET);
self.register.bits |=
((bits & MASK) as #reg_ty) << OFFSET;
self.register
}
});
}
mod_items.push(quote! {
impl<'a> #proxy<'a> {
#(#proxy_items)*
}
});
let doc = field_doc(field.bit_range,
field.description.as_ref());
w_impl_items.push(quote! {
#[doc = #doc]
pub fn #field_name_sc(&mut self) -> #proxy {
#proxy {
register: self,
}
}
});
}
}
}
if access == Access::ReadOnly || access == Access::ReadWrite {
mod_items.push(quote! {
impl R {
#(#r_impl_items)*
}
});
}
if access == Access::WriteOnly || access == Access::ReadWrite {
mod_items.push(quote! {
impl W {
#(#w_impl_items)*
}
});
}
let doc = respace(&r.description);
items.push(quote! {
#[doc = #doc]
pub mod #name_sc {
#(#mod_items)*
}
});
items
}
fn lookup<'a>(evs: &'a [EnumeratedValues],
fields: &'a [Field],
all_registers: &'a [Register],
usage: Usage)
-> Option<(&'a EnumeratedValues, Option<Base<'a>>)> {
match evs.first() {
Some(head) if evs.len() == 1 => Some(head),
None => None,
_ => evs.iter().find(|ev| ev.usage == Some(usage)),
}
.map(|evs| {
if let Some(ref base) = evs.derived_from {
let mut parts = base.split('.');
let (register, fields, field) = match (parts.next(),
parts.next()) {
(Some(register), Some(field)) => {
let fields = all_registers.iter()
.find(|r| r.name == register)
.expect("couldn't find register")
.fields
.as_ref()
.expect("no fields");
(Some(register), &fields[..], field)
}
(Some(field), None) => (None, fields, field),
_ => unreachable!(),
};
let evs = fields.iter()
.flat_map(|f| f.enumerated_values.iter())
.find(|evs| evs.name.as_ref().map(|s| &**s) == Some(field))
.expect("");
(evs,
Some(Base {
register: register,
field: field,
}))
} else {
(evs, None)
}
})
}
fn field_doc(bit_range: BitRange, doc: Option<&String>) -> String {
let BitRange { offset, width } = bit_range;
if let Some(doc) = doc {
let doc = respace(doc);
if width == 1 {
format!("Bit {} - {}", offset, doc)
} else {
format!("Bits {}:{} - {}", offset, offset + width - 1, doc)
}
} else if width == 1 {
format!("Bit {}", offset)
} else {
format!("Bits {}:{}", offset, offset + width - 1)
}
}
struct Base<'a> {
register: Option<&'a str>,
field: &'a str,
}
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(" ")
}