Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions examples/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ struct Alignable {
#[derive(FormatArgs)]
struct WithBounds<'a, T: std::fmt::Display + 'a>(&'a T);

#[allow(dead_code)]
#[derive(FormatArgs)]
struct WithAttributes {
#[format_args(rename = "renamed_field")]
field1: &'static str,
#[format_args(aliases = "alias")]
field2: &'static str,
#[format_args(ignore)]
ignored: &'static str
}

#[derive(FormatArgs)]
struct TupleWithAttributes(#[format_args(rename = "field1")] i32);

fn main() {
let mut prepared = PreparedFormat::prepare("{left}: {right}").unwrap();
prepared.newln();
Expand All @@ -49,4 +63,24 @@ fn main() {
});

PreparedFormat::prepare("{}").unwrap().newln().print(&WithBounds(&256));

let mut prepared = PreparedFormat::prepare(
r#"WithAttributes {{
renamed_field: {renamed_field}
field2: {field2}
alias: {alias}
}}"#).unwrap();
prepared.newln();
prepared.print(&WithAttributes {
field1: "field1",
field2: "field2",
ignored: "ignored"
});

match PreparedFormat::<WithAttributes>::prepare("{ignored}") {
Ok(_) => panic!("Field 'ignored' is not ignored"),
_ => { }
}

PreparedFormat::prepare("TupleWithAttributes({field1})").unwrap().newln().print(&TupleWithAttributes(256));
}
202 changes: 202 additions & 0 deletions runtime-fmt-derive/src/ast.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@

use std::borrow::{Borrow, Cow};
use std::collections::HashSet;
use syn;
use syn::Lit::Str;
use syn::MetaItem::{List, NameValue, Word};
use syn::NestedMetaItem::MetaItem;
use ::context::Context;

struct Attribute<'a, T> {
context: &'a Context,
name: &'static str,
value: Option<T>
}

impl<'a, T> Attribute<'a, T> {

fn new(context: &'a Context, name: &'static str) -> Self {
Attribute {
context: context,
name: name,
value: None
}
}

fn set(&mut self, value: T) {
if self.value.is_some() {
self.context.error(&format!("Duplicate attribute provided: {}", self.name));
}
else {
self.value = Some(value);
}
}

fn into(mut self) -> Option<T> {
self.value.take()
}

}

struct BoolAttribute<'a> {
inner: Attribute<'a, ()>
}

impl<'a> BoolAttribute<'a> {

fn new(context: &'a Context, name: &'static str) -> Self {
BoolAttribute {
inner: Attribute::new(context, name)
}
}

fn set(&mut self) {
self.inner.set(());
}

fn into(self) -> bool {
self.inner.into().is_some()
}

}

pub struct Container<'a> {
ast: &'a syn::DeriveInput,

fields: Vec<Field<'a>>
}

impl<'a> Container<'a> {

pub fn from_ast(ast: &'a syn::DeriveInput) -> Result<Self, String> {
let ctx = Context::new();

let variant = match ast.body {
syn::Body::Struct(ref variant) => variant,
_ => return Err("#[derive(FormatArgs)] is not implemented for enums".to_string())
};

let fields = {
let mut fields = Vec::new();
let mut field_names = HashSet::new();
for (tuple_index, field) in variant.fields().iter().enumerate() {
if let Some(field) = Field::from_ast(&ctx, field, fields.len(), tuple_index) {
for alias in field.aliases() {
if !field_names.insert(*alias) {
ctx.error(&format!("Duplicate field alias: {}", alias));
}
}
fields.push(field);
}
}

fields
};

ctx.check().map(|_| {
Container {
ast: ast,

fields: fields
}
})
}

pub fn ident(&self) -> &'a syn::Ident {
&self.ast.ident
}

pub fn generics(&self) -> &'a syn::Generics {
&self.ast.generics
}

pub fn fields(&self) -> &[Field<'a>] {
&self.fields
}

}

pub struct Field<'a> {
field_index: usize,

ident: Cow<'a, syn::Ident>,
aliases: Vec<&'a str>,

ty: &'a syn::Ty
}

impl<'a> Field<'a> {

pub fn from_ast(ctx: &Context, ast: &'a syn::Field, field_index: usize, tuple_index: usize) -> Option<Self> {
let ident = match ast.ident {
Some(ref ident) => Cow::Borrowed(ident),
None => Cow::Owned(syn::Ident::from(tuple_index))
};

let mut aliases = Attribute::new(ctx, "aliases");
let mut ignored = BoolAttribute::new(ctx, "ignore");
let mut name = Attribute::new(ctx, "rename");

for attributes in ast.attrs.iter().filter_map(filter_format_attributes) {
for attribute in attributes {
match *attribute {
MetaItem(NameValue(ref ident, Str(ref value, _))) if ident == "aliases" => {
aliases.set(value.split(",").collect());
}
MetaItem(NameValue(ref ident, Str(ref value, _))) if ident == "rename" => {
name.set(value.as_ref());
}
MetaItem(Word(ref ident)) if ident == "ignore" => {
ignored.set();
}
_ => ctx.error(&format!("Unrecognized attribute: {:?}", attribute))
}
}
}

let mut aliases = aliases.into().unwrap_or_else(Vec::new);
if let Some(name) = name.into().or(ast.ident.as_ref().map(|ident| ident.as_ref())) {
aliases.push(name);
}

if !ignored.into() {
Some(Field {
field_index: field_index,

ident: ident,
aliases: aliases,

ty: &ast.ty
})
}
else {
None
}
}

pub fn index(&self) -> usize {
self.field_index
}

pub fn ident(&self) -> &syn::Ident {
self.ident.borrow()
}

pub fn aliases(&self) -> &[&'a str] {
&self.aliases
}

pub fn ty(&self) -> &'a syn::Ty {
self.ty
}

}

fn filter_format_attributes(attr: &syn::Attribute) -> Option<&Vec<syn::NestedMetaItem>> {
match attr.value {
List(ref name, ref items) if name == "format_args" => {
Some(items)
}
_ => None
}
}
44 changes: 44 additions & 0 deletions runtime-fmt-derive/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

use std::cell::RefCell;

pub struct Context {
error: RefCell<Option<String>>
}

impl Context {

pub fn new() -> Self {
Context {
error: RefCell::new(Some(String::new()))
}
}

pub fn error(&self, error: &str) {
let mut cur_error = self.error.borrow_mut();
let mut cur_error = cur_error.get_or_insert_with(String::new);

if !cur_error.is_empty() {
cur_error.push('\n');
}
*cur_error += &error;
}

pub fn check(&self) -> Result<(), String> {
self.error.borrow_mut()
.take().into_iter()
.filter(|s| !s.is_empty())
.next()
.map_or(Ok(()), Err)
}

}

impl Drop for Context {

fn drop(&mut self) {
if self.error.borrow().is_some() {
panic!("Failed to check for errors in context");
}
}

}
Loading