diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 3a315ef952d70..0179013234d8f 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -224,13 +224,16 @@ pub fn phase_3_run_analysis_passes(sess: Session, let lang_items = time(time_passes, ~"language item collection", || middle::lang_items::collect_language_items(crate, sess)); + let fmt_traits = time(time_passes, ~"finding fmt traits", || + middle::fmt::find_fmt_traits(crate, sess)); + let middle::resolve::CrateMap { def_map: def_map, exp_map2: exp_map2, trait_map: trait_map } = time(time_passes, ~"resolution", || - middle::resolve::resolve_crate(sess, lang_items, crate)); + middle::resolve::resolve_crate(sess, lang_items, fmt_traits, crate)); time(time_passes, ~"looking for entry point", || middle::entry::find_entry_point(sess, crate, ast_map)); @@ -245,7 +248,7 @@ pub fn phase_3_run_analysis_passes(sess: Session, middle::region::determine_rp_in_crate(sess, ast_map, def_map, crate)); let ty_cx = ty::mk_ctxt(sess, def_map, ast_map, freevars, - region_map, rp_set, lang_items); + region_map, rp_set, lang_items, fmt_traits); // passes are timed inside typeck let (method_map, vtable_map) = typeck::check_crate( diff --git a/src/librustc/metadata/common.rs b/src/librustc/metadata/common.rs index 1c5d202d4d953..28abc02ee9736 100644 --- a/src/librustc/metadata/common.rs +++ b/src/librustc/metadata/common.rs @@ -182,6 +182,12 @@ pub static tag_misc_info_crate_items: uint = 0x80; pub static tag_item_method_provided_source: uint = 0x81; pub static tag_item_impl_vtables: uint = 0x82; +pub static tag_fmt_traits: uint = 0x83; +pub static tag_fmt_traits_item: uint = 0x84; +pub static tag_fmt_traits_item_node_id: uint = 0x85; +pub static tag_fmt_traits_item_trait_id: uint = 0x86; +pub static tag_fmt_traits_item_name: uint = 0x87; + pub struct LinkMeta { name: @str, vers: @str, diff --git a/src/librustc/metadata/csearch.rs b/src/librustc/metadata/csearch.rs index 317b9cf6ce34f..6af867b426640 100644 --- a/src/librustc/metadata/csearch.rs +++ b/src/librustc/metadata/csearch.rs @@ -49,6 +49,16 @@ pub fn each_lang_item(cstore: @mut cstore::CStore, decoder::each_lang_item(crate_data, f) } +/// Iterates over all the fmt traits in the given crate. The yielded arguments +/// are the format specifier for the trait, the NodeId of the `fmt` function, +/// and the NodeId of the trait with the fmt function. +pub fn each_fmt_trait(cstore: @mut cstore::CStore, + cnum: ast::CrateNum, + f: &fn(@str, ast::NodeId, ast::NodeId) -> bool) -> bool { + let crate_data = cstore::get_crate_data(cstore, cnum); + decoder::each_fmt_trait(crate_data, f) +} + /// Iterates over all the paths in the given crate. pub fn each_path(cstore: @mut cstore::CStore, cnum: ast::CrateNum, diff --git a/src/librustc/metadata/decoder.rs b/src/librustc/metadata/decoder.rs index 3a8b9a27f1cac..3f427417264f4 100644 --- a/src/librustc/metadata/decoder.rs +++ b/src/librustc/metadata/decoder.rs @@ -479,6 +479,35 @@ pub fn each_lang_item(cdata: cmd, f: &fn(ast::NodeId, uint) -> bool) -> bool { return true; } +/// Iterates over the fmt trait items in the given crate. +#[cfg(stage0)] +pub fn each_fmt_trait(_: cmd, _: &fn(@str, ast::NodeId, ast::NodeId) -> bool) -> bool { + true +} + +/// Iterates over the fmt trait items in the given crate. +#[cfg(not(stage0))] +pub fn each_fmt_trait(cdata: cmd, + f: &fn(@str, ast::NodeId, ast::NodeId) -> bool) -> bool { + let root = reader::Doc(cdata.data); + let fmt_traits = reader::get_doc(root, tag_fmt_traits); + for reader::tagged_docs(fmt_traits, tag_fmt_traits_item) |trait_doc| { + let id_doc = reader::get_doc(trait_doc, tag_fmt_traits_item_node_id); + let node_id = reader::doc_as_u32(id_doc) as ast::NodeId; + + let trait_id_doc = reader::get_doc(trait_doc, tag_fmt_traits_item_trait_id); + let trait_id = reader::doc_as_u32(trait_id_doc) as ast::NodeId; + + let name_doc = reader::get_doc(trait_doc, tag_fmt_traits_item_name); + let name = name_doc.as_str().to_managed(); + + if !f(name, node_id, trait_id) { + return false; + } + } + return true; +} + struct EachItemContext<'self> { intr: @ident_interner, cdata: cmd, diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index 90604cf36cb5c..ec2e287457626 100644 --- a/src/librustc/metadata/encoder.rs +++ b/src/librustc/metadata/encoder.rs @@ -72,6 +72,7 @@ struct Stats { dep_bytes: uint, lang_item_bytes: uint, link_args_bytes: uint, + fmt_trait_bytes: uint, misc_bytes: uint, item_bytes: uint, index_bytes: uint, @@ -1478,6 +1479,37 @@ fn encode_lang_items(ecx: &EncodeContext, ebml_w: &mut writer::Encoder) { ebml_w.end_tag(); // tag_lang_items } +fn encode_fmt_traits(ecx: &EncodeContext, ebml_w: &mut writer::Encoder) { + ebml_w.start_tag(tag_fmt_traits); + + for ecx.tcx.fmt_traits.iter().advance |(name, &(def_id, def))| { + if !is_local(def_id) { loop } + + ebml_w.start_tag(tag_fmt_traits_item); + + ebml_w.start_tag(tag_fmt_traits_item_node_id); + ebml_w.writer.write_be_u32(def_id.node as u32); + ebml_w.end_tag(); // tag_fmt_traits_item_id + + match def { + ast::def_static_method(_, Some(trait_def_id), _) => { + ebml_w.start_tag(tag_fmt_traits_item_trait_id); + ebml_w.writer.write_be_u32(trait_def_id.node as u32); + ebml_w.end_tag(); // tag_fmt_traits_item_trait_id + } + _ => fail!() + } + + ebml_w.start_tag(tag_fmt_traits_item_name); + ebml_w.writer.write_str(*name); + ebml_w.end_tag(); // tag_fmt_traits_item_name + + ebml_w.end_tag(); // tag_fmt_traits_item + } + + ebml_w.end_tag(); // tag_fmt_traits +} + fn encode_link_args(ecx: &EncodeContext, ebml_w: &mut writer::Encoder) { ebml_w.start_tag(tag_link_args); @@ -1554,6 +1586,7 @@ pub fn encode_metadata(parms: EncodeParams, crate: &Crate) -> ~[u8] { dep_bytes: 0, lang_item_bytes: 0, link_args_bytes: 0, + fmt_trait_bytes: 0, misc_bytes: 0, item_bytes: 0, index_bytes: 0, @@ -1612,6 +1645,11 @@ pub fn encode_metadata(parms: EncodeParams, crate: &Crate) -> ~[u8] { encode_link_args(&ecx, &mut ebml_w); ecx.stats.link_args_bytes = *wr.pos - i; + // Encode the format traits. + i = *wr.pos; + encode_fmt_traits(&ecx, &mut ebml_w); + ecx.stats.fmt_trait_bytes = *wr.pos - i; + // Encode miscellaneous info. i = *wr.pos; encode_misc_info(&ecx, crate, &mut ebml_w); @@ -1643,6 +1681,7 @@ pub fn encode_metadata(parms: EncodeParams, crate: &Crate) -> ~[u8] { printfln!(" attribute bytes: %u", ecx.stats.attr_bytes); printfln!(" dep bytes: %u", ecx.stats.dep_bytes); printfln!(" lang item bytes: %u", ecx.stats.lang_item_bytes); + printfln!(" fmt trait bytes: %u", ecx.stats.fmt_trait_bytes); printfln!(" link args bytes: %u", ecx.stats.link_args_bytes); printfln!(" misc bytes: %u", ecx.stats.misc_bytes); printfln!(" item bytes: %u", ecx.stats.item_bytes); diff --git a/src/librustc/middle/cfg/construct.rs b/src/librustc/middle/cfg/construct.rs index 97fc49b23d022..da53093f6ee01 100644 --- a/src/librustc/middle/cfg/construct.rs +++ b/src/librustc/middle/cfg/construct.rs @@ -406,6 +406,7 @@ impl CFGBuilder { ast::expr_mac(*) | ast::expr_inline_asm(*) | + ast::expr_extfmt_fn(*) | ast::expr_self | ast::expr_fn_block(*) | ast::expr_lit(*) | diff --git a/src/librustc/middle/dataflow.rs b/src/librustc/middle/dataflow.rs index 1da3b31c93634..e82191cb1e65d 100644 --- a/src/librustc/middle/dataflow.rs +++ b/src/librustc/middle/dataflow.rs @@ -750,6 +750,7 @@ impl<'self, O:DataFlowOperator> PropagationContext<'self, O> { ast::expr_lit(*) | ast::expr_path(*) | + ast::expr_extfmt_fn(*) | ast::expr_self => { } diff --git a/src/librustc/middle/fmt.rs b/src/librustc/middle/fmt.rs new file mode 100644 index 0000000000000..6635b7c3e81a2 --- /dev/null +++ b/src/librustc/middle/fmt.rs @@ -0,0 +1,183 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This module implements the collection of #[fmt]-tagged traits in both the +//! local crate and external crates. This merges these two lists of fmt-traits +//! into one `TraitMap` which is then used during resolve and finally shoved +//! into the tcx. + +use driver::session::Session; +use metadata::csearch; +use metadata::cstore::iter_crate_data; + +use syntax::ast; +use syntax::ast_util; +use syntax::attr::AttrMetaMethods; +use syntax::codemap::span; +use syntax::visit; +use syntax::parse::token::str_to_ident; + +use std::hashmap::HashMap; +use std::util; + +/// Mapping of format string names to a (def_id, def) pair. The def_id is the +/// actual id of the formatting function while the `def` is the definition +/// corresponding for it. +pub type TraitMap = HashMap<@str, (ast::def_id, ast::def)>; + +struct Context { + /// Cached ident for the word "fmt" (what the format function is called) + fmt_ident: ast::ident, + sess: Session, + + /// The `fmts` map is returned at the end of the collection, and in the + /// interim the `fmt_loc` map keeps track of where the traits came from so + /// collisions can be diagnosed sanely. + fmts: TraitMap, + fmt_loc: HashMap<@str, Either>, +} + +impl Context { + /// Registers a new format-trait + fn register(&mut self, name: @str, src: Either, + fmt_id: ast::def_id, trait_id: ast::def_id) { + match self.fmts.find(&name) { + Some(*) => { + match src { + Left(sp) => { + self.sess.span_err(sp, fmt!("duplicate fmt trait \ + for `%s`", name)); + } + Right(src) => { + self.sess.err(fmt!("duplicate fmt trait `%s` found in \ + external crate `%s`", name, src)); + } + } + match *self.fmt_loc.get(&name) { + Left(sp) => { + self.sess.span_note(sp, "previous definition here"); + } + Right(name) => { + self.sess.note(fmt!("previous definition found in the \ + external crate `%s`", name)); + } + } + return; + } + None => {} + } + let def = ast::def_static_method(fmt_id, Some(trait_id), + ast::impure_fn); + debug!("found %s at %?", name, src); + self.fmts.insert(name, (fmt_id, def)); + self.fmt_loc.insert(name, src); + } + + /// Iterates over the local crate to identify all #[fmt]-tagged traits and + /// adds them to the local trait map + fn collect_local_traits(@mut self, crate: &ast::Crate) { + visit::visit_crate(crate, (self, visit::mk_vt(@visit::Visitor { + visit_item: |it, (cx, vt)| { + let cx: @mut Context = cx; + for it.attrs.iter().advance |attr| { + if "fmt" != attr.name() { loop } + match attr.value_str() { + Some(name) => { + match it.node { + ast::item_trait(_, _, ref methods) => { + cx.find_fmt_method(name, it.span, it.id, + *methods); + } + _ => { + cx.sess.span_err(attr.span, + "fmt attribute can only be \ + specified on traits"); + } + } + } + None => { + cx.sess.span_err(attr.span, + "fmt attribute must have a value \ + specified (fmt=\"...\")"); + } + } + } + visit::visit_item(it, (cx, vt)); + }, + .. *visit::default_visitor() + }))); + } + + fn find_fmt_method(@mut self, name: @str, sp: span, id: ast::NodeId, + methods: &[ast::trait_method]) { + fn check_purity(sess: Session, p: ast::purity, sp: span) { + match p { + ast::unsafe_fn => { + sess.span_err(sp, "the `fmt` function must not be unsafe"); + } + ast::extern_fn => { + sess.span_err(sp, "the `fmt` function must not be extern"); + } + ast::impure_fn => {} + } + } + + let mut found = false; + for methods.iter().advance |m| { + match *m { + ast::required(ref m) if m.ident == self.fmt_ident => { + check_purity(self.sess, m.purity, sp); + self.register(name, Left(sp), ast_util::local_def(m.id), + ast_util::local_def(id)); + found = true; + } + ast::provided(ref m) if m.ident == self.fmt_ident => { + check_purity(self.sess, m.purity, sp); + self.register(name, Left(sp), ast_util::local_def(m.id), + ast_util::local_def(id)); + found = true; + } + + ast::provided(*) | ast::required(*) => {} + } + } + if !found { + self.sess.span_err(sp, "no function named `fmt` on format trait"); + } + } + + /// Iterates over all of the external crates and loads all of the external + /// fmt traits into the local maps. + fn collect_external_traits(&mut self) { + let cstore = self.sess.cstore; + do iter_crate_data(cstore) |n, metadata| { + for csearch::each_fmt_trait(cstore, n) |name, fmt_id, trait_id| { + let fmt_id = ast::def_id { crate: n, node: fmt_id }; + let trait_id = ast::def_id { crate: n, node: trait_id }; + self.register(name, Right(metadata.name), fmt_id, trait_id); + } + } + } +} + +pub fn find_fmt_traits(crate: &ast::Crate, session: Session) -> @TraitMap { + let cx = @mut Context { + fmt_ident: str_to_ident("fmt"), + sess: session, + fmts: HashMap::new(), + fmt_loc: HashMap::new(), + }; + + cx.collect_external_traits(); + cx.collect_local_traits(crate); + session.abort_if_errors(); + + @util::replace(&mut cx.fmts, HashMap::new()) +} diff --git a/src/librustc/middle/kind.rs b/src/librustc/middle/kind.rs index 715ebc48a6fc9..1f8bdcde767c7 100644 --- a/src/librustc/middle/kind.rs +++ b/src/librustc/middle/kind.rs @@ -257,7 +257,7 @@ pub fn check_expr(e: @expr, (cx, v): (Context, visit::vt)) { let r = cx.tcx.node_type_substs.find(&type_parameter_id); for r.iter().advance |ts| { let type_param_defs = match e.node { - expr_path(_) => { + expr_path(*) | expr_extfmt_fn(*) => { let did = ast_util::def_id_of_def(cx.tcx.def_map.get_copy(&e.id)); ty::lookup_item_type(cx.tcx, did).generics.type_param_defs } diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index 385f23a73d6c4..24ef9643fef40 100644 --- a/src/librustc/middle/liveness.rs +++ b/src/librustc/middle/liveness.rs @@ -516,7 +516,7 @@ fn visit_expr(expr: @expr, (this, vt): (@mut IrMaps, vt<@mut IrMaps>)) { expr_again(_) | expr_lit(_) | expr_ret(*) | expr_block(*) | expr_assign(*) | expr_assign_op(*) | expr_mac(*) | expr_struct(*) | expr_repeat(*) | expr_paren(*) | - expr_inline_asm(*) => { + expr_inline_asm(*) | expr_extfmt_fn(*) => { visit::visit_expr(expr, (this, vt)); } } @@ -1223,7 +1223,7 @@ impl Liveness { } } - expr_lit(*) => { + expr_lit(*) | expr_extfmt_fn(*) => { succ } @@ -1484,7 +1484,8 @@ fn check_expr(expr: @expr, (this, vt): (@Liveness, vt<@Liveness>)) { expr_cast(*) | expr_unary(*) | expr_ret(*) | expr_break(*) | expr_again(*) | expr_lit(_) | expr_block(*) | expr_mac(*) | expr_addr_of(*) | expr_struct(*) | expr_repeat(*) | - expr_paren(*) | expr_fn_block(*) | expr_path(*) | expr_self(*) => { + expr_paren(*) | expr_fn_block(*) | expr_path(*) | expr_self(*) | + expr_extfmt_fn(*) => { visit::visit_expr(expr, (this, vt)); } } diff --git a/src/librustc/middle/mem_categorization.rs b/src/librustc/middle/mem_categorization.rs index 1c5f3b9bfdf18..54ef6499b9127 100644 --- a/src/librustc/middle/mem_categorization.rs +++ b/src/librustc/middle/mem_categorization.rs @@ -432,7 +432,7 @@ impl mem_categorization_ctxt { ast::expr_block(*) | ast::expr_loop(*) | ast::expr_match(*) | ast::expr_lit(*) | ast::expr_break(*) | ast::expr_mac(*) | ast::expr_again(*) | ast::expr_struct(*) | ast::expr_repeat(*) | - ast::expr_inline_asm(*) => { + ast::expr_inline_asm(*) | ast::expr_extfmt_fn(*) => { return self.cat_rvalue_node(expr, expr_ty); } } diff --git a/src/librustc/middle/moves.rs b/src/librustc/middle/moves.rs index 1cf0162d784dc..103466c8f1d7b 100644 --- a/src/librustc/middle/moves.rs +++ b/src/librustc/middle/moves.rs @@ -469,6 +469,7 @@ impl VisitContext { } expr_inline_asm(*) | + expr_extfmt_fn(*) | expr_break(*) | expr_again(*) | expr_lit(*) => {} diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs index 4f7a2f67b3cf5..a3fe310b5623c 100644 --- a/src/librustc/middle/resolve.rs +++ b/src/librustc/middle/resolve.rs @@ -18,6 +18,7 @@ use metadata::cstore::find_extern_mod_stmt_cnum; use metadata::decoder::{def_like, dl_def, dl_field, dl_impl}; use middle::lang_items::LanguageItems; use middle::lint::{unnecessary_qualification, unused_imports}; +use middle::fmt; use middle::pat_util::pat_bindings; use syntax::ast::*; @@ -764,6 +765,7 @@ pub fn namespace_error_to_str(ns: NamespaceError) -> &'static str { pub fn Resolver(session: Session, lang_items: LanguageItems, + fmt_traits: @fmt::TraitMap, crate: @Crate) -> Resolver { let graph_root = @mut NameBindings(); @@ -779,6 +781,7 @@ pub fn Resolver(session: Session, let this = Resolver { session: @session, lang_items: lang_items, + fmt_traits: fmt_traits, crate: crate, // The outermost module has def ID 0; this is not reflected in the @@ -821,6 +824,7 @@ pub fn Resolver(session: Session, pub struct Resolver { session: @Session, lang_items: LanguageItems, + fmt_traits: @fmt::TraitMap, crate: @Crate, intr: @ident_interner, @@ -5047,6 +5051,17 @@ impl Resolver { } } + expr_extfmt_fn(ref name) => { + match self.fmt_traits.find(name) { + None => { + self.session.span_err(expr.span, + fmt!("unknown format trait `%s`", + *name)); + } + Some(&(_, def)) => self.record_def(expr.id, def), + } + } + _ => { visit_expr(expr, ((), visitor)); } @@ -5407,9 +5422,10 @@ pub struct CrateMap { /// Entry point to crate resolution. pub fn resolve_crate(session: Session, lang_items: LanguageItems, + fmt_traits: @fmt::TraitMap, crate: @Crate) -> CrateMap { - let resolver = @mut Resolver(session, lang_items, crate); + let resolver = @mut Resolver(session, lang_items, fmt_traits, crate); resolver.resolve(); CrateMap { def_map: resolver.def_map, diff --git a/src/librustc/middle/trans/expr.rs b/src/librustc/middle/trans/expr.rs index b657f162cc3c0..b6f5e25b588e6 100644 --- a/src/librustc/middle/trans/expr.rs +++ b/src/librustc/middle/trans/expr.rs @@ -451,7 +451,7 @@ fn trans_rvalue_datum_unadjusted(bcx: @mut Block, expr: @ast::expr) -> DatumBloc trace_span!(bcx, expr.span, shorten(bcx.expr_to_str(expr))); match expr.node { - ast::expr_path(_) | ast::expr_self => { + ast::expr_path(*) | ast::expr_self | ast::expr_extfmt_fn(*) => { return trans_def_datum_unadjusted(bcx, expr, bcx.def(expr.id)); } ast::expr_vstore(contents, ast::expr_vstore_box) | diff --git a/src/librustc/middle/trans/type_use.rs b/src/librustc/middle/trans/type_use.rs index 3df91844ddad8..f31ed3812e010 100644 --- a/src/librustc/middle/trans/type_use.rs +++ b/src/librustc/middle/trans/type_use.rs @@ -327,7 +327,7 @@ pub fn mark_for_expr(cx: &Context, e: &expr) { _ => () } } - expr_path(_) | expr_self => { + expr_extfmt_fn(*) | expr_path(_) | expr_self => { let opt_ts = cx.ccx.tcx.node_type_substs.find_copy(&e.id); for opt_ts.iter().advance |ts| { let id = ast_util::def_id_of_def(cx.ccx.tcx.def_map.get_copy(&e.id)); diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 050b6d3fddefc..beb97bc4a7235 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -282,6 +282,7 @@ struct ctxt_ { adjustments: @mut HashMap, normalized_cache: @mut HashMap, lang_items: middle::lang_items::LanguageItems, + fmt_traits: @middle::fmt::TraitMap, // A mapping of fake provided method def_ids to the default implementation provided_method_sources: @mut HashMap, supertraits: @mut HashMap, @@ -879,7 +880,8 @@ pub fn mk_ctxt(s: session::Session, freevars: freevars::freevar_map, region_maps: @mut middle::region::RegionMaps, region_paramd_items: middle::region::region_paramd_items, - lang_items: middle::lang_items::LanguageItems) + lang_items: middle::lang_items::LanguageItems, + fmt_traits: @middle::fmt::TraitMap) -> ctxt { @ctxt_ { diag: s.diagnostic(), @@ -913,6 +915,7 @@ pub fn mk_ctxt(s: session::Session, adjustments: @mut HashMap::new(), normalized_cache: new_ty_hash(), lang_items: lang_items, + fmt_traits: fmt_traits, provided_method_sources: @mut HashMap::new(), supertraits: @mut HashMap::new(), destructor_for_type: @mut HashMap::new(), @@ -3153,7 +3156,7 @@ pub fn expr_kind(tcx: ctxt, } match expr.node { - ast::expr_path(*) | ast::expr_self => { + ast::expr_path(*) | ast::expr_self | ast::expr_extfmt_fn(*) => { match resolve_expr(tcx, expr) { ast::def_variant(*) | ast::def_struct(*) => RvalueDpsExpr, diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 9746993734007..d150d6b38b9f0 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -2471,6 +2471,19 @@ pub fn check_expr_with_unifier(fcx: @mut FnCtxt, let tpt = ty_param_bounds_and_ty_for_def(fcx, expr.span, defn); instantiate_path(fcx, pth, tpt, expr.span, expr.id); } + ast::expr_extfmt_fn(ref name) => { + // emulate the functionality of expr_path(*), but we have some + // hardcoded assumptions here (about path tys and the def_id) + let (def_id, _) = *fcx.tcx().fmt_traits.get(name); + let tpt = ty::lookup_item_type(fcx.tcx(), def_id); + let tps = fcx.infcx().next_ty_vars(tpt.generics.type_param_defs.len()); + let regions = fcx.region_var_if_parameterized(tpt.generics.region_param, + expr.span); + let substs = substs {regions: ty::NonerasedRegions(regions), + self_ty: None, + tps: tps }; + fcx.write_ty_substs(id, tpt.ty, substs); + } ast::expr_self => { let definition = lookup_def(fcx, expr.span, id); let ty_param_bounds_and_ty = diff --git a/src/librustc/middle/typeck/check/regionck.rs b/src/librustc/middle/typeck/check/regionck.rs index 1f4c37a783feb..5d4cb166e2daa 100644 --- a/src/librustc/middle/typeck/check/regionck.rs +++ b/src/librustc/middle/typeck/check/regionck.rs @@ -1010,6 +1010,7 @@ pub mod guarantor { // All of these expressions are rvalues and hence their // value is not guaranteed by a region pointer. ast::expr_inline_asm(*) | + ast::expr_extfmt_fn(*) | ast::expr_mac(*) | ast::expr_lit(_) | ast::expr_unary(*) | diff --git a/src/librustc/middle/typeck/check/vtable.rs b/src/librustc/middle/typeck/check/vtable.rs index 3bae934f27078..04373c63b85d2 100644 --- a/src/librustc/middle/typeck/check/vtable.rs +++ b/src/librustc/middle/typeck/check/vtable.rs @@ -551,7 +551,7 @@ pub fn early_resolve_expr(ex: @ast::expr, let cx = fcx.ccx; match ex.node { - ast::expr_path(*) => { + ast::expr_path(*) | ast::expr_extfmt_fn(*) => { for fcx.opt_node_ty_substs(ex.id) |substs| { debug!("vtable resolution on parameter bounds for expr %s", ex.repr(fcx.tcx())); diff --git a/src/librustc/rustc.rs b/src/librustc/rustc.rs index 6604a8bc74be0..6c590759a40d0 100644 --- a/src/librustc/rustc.rs +++ b/src/librustc/rustc.rs @@ -61,6 +61,7 @@ pub mod middle { pub mod const_eval; pub mod astencode; pub mod lang_items; + pub mod fmt; pub mod privacy; pub mod moves; pub mod entry; diff --git a/src/libstd/either.rs b/src/libstd/either.rs index 4fb43e5157b43..6ee59cf4fdb51 100644 --- a/src/libstd/either.rs +++ b/src/libstd/either.rs @@ -23,7 +23,7 @@ use vec; use vec::{OwnedVector, ImmutableVector}; /// The either type -#[deriving(Clone, Eq)] +#[deriving(Clone, Eq, IterBytes)] pub enum Either { Left(T), Right(U) diff --git a/src/libstd/fmt/ct.rs b/src/libstd/fmt/ct.rs new file mode 100644 index 0000000000000..420056c571f87 --- /dev/null +++ b/src/libstd/fmt/ct.rs @@ -0,0 +1,901 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use prelude::*; + +use char; +use str; +use iterator; + +// FIXME(#8179): it would be awesome to raise errors through conditions, but +// they don't work cross-crate yet +// condition! { pub parse_error: ~str -> (); } + +/// A piece is a portion of the format string which represents the next part to +/// emit. These are emitted as a stream by the `Parser` class. +#[deriving(Eq)] +pub enum Piece<'self> { + /// A literal string which should directly be emitted + String(&'self str), + /// A back-reference to whatever the current argument is. This is used + /// inside of a method call to refer back to the original argument. + CurrentArgument, + /// This describes that formatting should process the next argument (as + /// specified inside) for emission. + Argument(Argument<'self>), +} + +/// Representation of an argument specification. +#[deriving(Eq)] +pub struct Argument<'self> { + /// Where to find this argument + position: Position<'self>, + /// How to format the argument + format: FormatSpec<'self>, + /// If not `None`, what method to invoke on the argument + method: Option<~Method<'self>> +} + +/// Specification for the formatting of an argument in the format string. +#[deriving(Eq)] +pub struct FormatSpec<'self> { + /// Optionally specified character to fill alignment with + fill: Option, + /// Optionally specified alignment + align: Option, + /// Packed version of various flags provided + flags: uint, + /// The integer precision to use + precision: Count, + /// The string width requested for the resulting format + width: Count, + /// The descriptor string representing the name of the format desired for + /// this argument, this can be empty or any number of characters, although + /// it is required to be one word. + ty: &'self str +} + +/// Enum describing where an argument for a format can be located. +#[deriving(Eq)] +pub enum Position<'self> { + ArgumentNext, ArgumentIs(uint), ArgumentNamed(&'self str) +} + +/// Enum of alignments which are supoprted. +#[deriving(Eq)] +pub enum Alignment { AlignLeft, AlignRight } + +/// Various flags which can be applied to format strings, the meaning of these +/// flags is defined by the formatters themselves. +#[deriving(Eq)] +pub enum Flag { + FlagSignPlus, + FlagSignMinus, + FlagAlternate, +} + +/// A count is used for the precision and width parameters of a in integer, and +/// can reference either an argument or a literal integer. +#[deriving(Eq)] +pub enum Count { + CountIs(uint), + CountIsParam(uint), + CountIsNextParam, + CountImplied, +} + +/// Enum describing all of the possible methods which the formatting language +/// currently supports. +#[deriving(Eq)] +pub enum Method<'self> { + /// A plural method selects on an integer over a list of either integer or + /// keyword-defined clauses. The meaning of the keywords is defined by the + /// current locale. + /// + /// An offset is optionally present at the beginning which is used to match + /// against keywords, but it is not matched against the literal integers. + /// + /// The final element of this enum is the default "other" case which is + /// always required to be specified. + Plural(Option, ~[PluralArm<'self>], ~[Piece<'self>]), + + /// A select method selects over a string. Each arm is a different string + /// which can be selected for. + /// + /// As with `Plural`, a default "other" case is required as well. + Select(~[SelectArm<'self>], ~[Piece<'self>]), +} + +/// Structure representing one "arm" of the `plural` function. +#[deriving(Eq)] +pub struct PluralArm<'self> { + /// A selector can either be specified by a keyword or with an integer + /// literal. + selector: Either, + /// Array of pieces which are the format of this arm + result: ~[Piece<'self>], +} + +/// Enum of the 5 CLDR plural keywords. There is one more, "other", but that is +/// specially placed in the `Plural` variant of `Method` +/// +/// http://www.icu-project.org/apiref/icu4c/classicu_1_1PluralRules.html +#[deriving(Eq, IterBytes)] +pub enum PluralKeyword { + Zero, One, Two, Few, Many +} + +/// Structure representing one "arm" of the `select` function. +#[deriving(Eq)] +pub struct SelectArm<'self> { + /// String selector which guards this arm + selector: &'self str, + /// Array of pieces which are the format of this arm + result: ~[Piece<'self>], +} + +/// The parser structure for interpreting the input format string. This is +/// modelled as an iterator over `Piece` structures to form a stream of tokens +/// being output. +/// +/// This is a recursive-descent parser for the sake of simplicity, and if +/// necessary there's probably lots of room for improvement performance-wise. +pub struct Parser<'self> { + priv input: &'self str, + priv cur: str::CharOffsetIterator<'self>, + // FIXME(#8179): use a condition instead (see above) + priv err_fn: &'self fn(&str), +} + +impl<'self> iterator::Iterator> for Parser<'self> { + fn next(&mut self) -> Option> { + match self.cur.clone().next() { + Some((_, '#')) => { self.cur.next(); Some(CurrentArgument) } + Some((_, '{')) => { + self.cur.next(); + let ret = Some(Argument(self.argument())); + if !self.consume('}') { + self.err(~"unterminated format string"); + } + ret + } + Some((pos, '\\')) => { + self.cur.next(); + self.escape(); // ensure it's a valid escape sequence + Some(String(self.string(pos + 1))) // skip the '\' character + } + Some((_, '}')) | None => { None } + Some((pos, _)) => { + Some(String(self.string(pos))) + } + } + } +} + +impl<'self> Parser<'self> { + /// Creates a new parser for the given format string + pub fn new<'a>(s: &'a str, err_fn: &'a fn(&str)) -> Parser<'a> { + Parser { + input: s, + cur: s.char_offset_iter(), + err_fn: err_fn, + } + } + + /// Notifies of an error. The message doesn't actually need to be of type + /// ~str, but I think it does when this eventually uses conditions so it + /// might as well start using it now. + fn err(&self, msg: ~str) { + (self.err_fn)(msg) + } + + /// Optionally consumes the specified character. If the character is not at + /// the current position, then the current iterator isn't moved and false is + /// returned, otherwise the character is consumed and true is returned. + fn consume(&mut self, c: char) -> bool { + match self.cur.clone().next() { + Some((_, maybe)) if c == maybe => { + self.cur.next(); + true + } + Some(*) | None => false, + } + } + + /// Attempts to consume any amount of whitespace followed by a character + fn wsconsume(&mut self, c: char) -> bool { + self.ws(); self.consume(c) + } + + /// Consumes all whitespace characters until the first non-whitespace + /// character + fn ws(&mut self) { + loop { + match self.cur.clone().next() { + Some((_, c)) if char::is_whitespace(c) => { self.cur.next(); } + Some(*) | None => { return } + } + } + } + + /// Consumes an escape sequence, failing if there is not a valid character + /// to be escaped. + fn escape(&mut self) -> char { + match self.cur.next() { + Some((_, c @ '#')) | Some((_, c @ '{')) | + Some((_, c @ '\\')) | Some((_, c @ '}')) => { c } + Some((_, c)) => { + self.err(fmt!("invalid escape character `%c`", c)); + c + } + None => { + self.err(~"expected an escape sequence, but format string was \ + terminated"); + ' ' + } + } + } + + /// Parses all of a string which is to be considered a "raw literal" in a + /// format string. This is everything outside of the braces. + fn string(&mut self, start: uint) -> &'self str { + loop { + // we may not consume the character, so clone the iterator + match self.cur.clone().next() { + Some((pos, '\\')) | Some((pos, '#')) | + Some((pos, '}')) | Some((pos, '{')) => { + return self.input.slice(start, pos); + } + Some(*) => { self.cur.next(); } + None => { + self.cur.next(); + return self.input.slice(start, self.input.len()); + } + } + } + } + + /// Parses an Argument structure, or what's contained within braces inside + /// the format string + fn argument(&mut self) -> Argument<'self> { + Argument { + position: self.position(), + format: self.format(), + method: self.method(), + } + } + + /// Parses a positional argument for a format. This could either be an + /// integer index of an argument, a named argument, or a blank string. + fn position(&mut self) -> Position<'self> { + match self.integer() { + Some(i) => { ArgumentIs(i) } + None => { + match self.cur.clone().next() { + Some((_, c)) if char::is_alphabetic(c) => { + ArgumentNamed(self.word()) + } + _ => ArgumentNext + } + } + } + } + + /// Parses a format specifier at the current position, returning all of the + /// relevant information in the FormatSpec struct. + fn format(&mut self) -> FormatSpec<'self> { + let mut spec = FormatSpec { + fill: None, + align: None, + flags: 0, + precision: CountImplied, + width: CountImplied, + ty: self.input.slice(0, 0), + }; + if !self.consume(':') { return spec } + + // fill character + match self.cur.clone().next() { + Some((_, c)) => { + match self.cur.clone().skip(1).next() { + Some((_, '>')) | Some((_, '<')) => { + spec.fill = Some(c); + self.cur.next(); + } + Some(*) | None => {} + } + } + None => {} + } + // Alignment + if self.consume('<') { + spec.align = Some(AlignLeft); + } else if self.consume('>') { + spec.align = Some(AlignRight); + } + // Sign flags + if self.consume('+') { + spec.flags |= 1 << (FlagSignPlus as uint); + } else if self.consume('-') { + spec.flags |= 1 << (FlagSignMinus as uint); + } + // Alternate marker + if self.consume('#') { + spec.flags |= 1 << (FlagAlternate as uint); + } + // Width and precision + spec.width = self.count(); + if self.consume('.') { + if self.consume('*') { + spec.precision = CountIsNextParam; + } else { + spec.precision = self.count(); + } + } + // Finally the actual format specifier + spec.ty = self.word(); + return spec; + } + + /// Parses a method to be applied to the previously specified argument and + /// its format. The two current supported methods are 'plural' and 'select' + fn method(&mut self) -> Option<~Method<'self>> { + if !self.wsconsume(',') { + return None; + } + self.ws(); + match self.word() { + "select" => { + if !self.wsconsume(',') { + self.err(~"`select` must be followed by `,`"); + } + Some(self.select()) + } + "plural" => { + if !self.wsconsume(',') { + self.err(~"`plural` must be followed by `,`"); + } + Some(self.plural()) + } + "" => { + self.err(~"expected method after comma"); + return None; + } + method => { + self.err(fmt!("unknown method: `%s`", method)); + return None; + } + } + } + + /// Parses a 'select' statement (after the initial 'select' word) + fn select(&mut self) -> ~Method<'self> { + let mut other = None; + let mut arms = ~[]; + // Consume arms one at a time + loop { + self.ws(); + let selector = self.word(); + if selector == "" { + self.err(~"cannot have an empty selector"); + break + } + if !self.wsconsume('{') { + self.err(~"selector must be followed by `{`"); + } + let pieces = self.collect(); + if !self.wsconsume('}') { + self.err(~"selector case must be terminated by `}`"); + } + if selector == "other" { + if !other.is_none() { + self.err(~"multiple `other` statements in `select"); + } + other = Some(pieces); + } else { + arms.push(SelectArm { selector: selector, result: pieces }); + } + self.ws(); + match self.cur.clone().next() { + Some((_, '}')) => { break } + Some(*) | None => {} + } + } + // The "other" selector must be present + let other = match other { + Some(arm) => { arm } + None => { + self.err(~"`select` statement must provide an `other` case"); + ~[] + } + }; + ~Select(arms, other) + } + + /// Parses a 'plural' statement (after the initial 'plural' word) + fn plural(&mut self) -> ~Method<'self> { + let mut offset = None; + let mut other = None; + let mut arms = ~[]; + + // First, attempt to parse the 'offset:' field. We know the set of + // selector words which can appear in plural arms, and the only ones + // which start with 'o' are "other" and "offset", hence look two + // characters deep to see if we can consume the word "offset" + self.ws(); + let mut it = self.cur.clone(); + match it.next() { + Some((_, 'o')) => { + match it.next() { + Some((_, 'f')) => { + let word = self.word(); + if word != "offset" { + self.err(fmt!("expected `offset`, found `%s`", + word)); + } else { + if !self.consume(':') { + self.err(~"`offset` must be followed by `:`"); + } + match self.integer() { + Some(i) => { offset = Some(i); } + None => { + self.err(~"offset must be an integer"); + } + } + } + } + Some(*) | None => {} + } + } + Some(*) | None => {} + } + + // Next, generate all the arms + loop { + let mut isother = false; + let selector = if self.wsconsume('=') { + match self.integer() { + Some(i) => Right(i), + None => { + self.err(~"plural `=` selectors must be followed by an \ + integer"); + Right(0) + } + } + } else { + let word = self.word(); + match word { + "other" => { isother = true; Left(Zero) } + "zero" => Left(Zero), + "one" => Left(One), + "two" => Left(Two), + "few" => Left(Few), + "many" => Left(Many), + word => { + self.err(fmt!("unexpected plural selector `%s`", word)); + if word == "" { + break + } else { + Left(Zero) + } + } + } + }; + if !self.wsconsume('{') { + self.err(~"selector must be followed by `{`"); + } + let pieces = self.collect(); + if !self.wsconsume('}') { + self.err(~"selector case must be terminated by `}`"); + } + if isother { + if !other.is_none() { + self.err(~"multiple `other` statements in `select"); + } + other = Some(pieces); + } else { + arms.push(PluralArm { selector: selector, result: pieces }); + } + self.ws(); + match self.cur.clone().next() { + Some((_, '}')) => { break } + Some(*) | None => {} + } + } + + let other = match other { + Some(arm) => { arm } + None => { + self.err(~"`plural` statement must provide an `other` case"); + ~[] + } + }; + ~Plural(offset, arms, other) + } + + /// Parses a Count parameter at the current position. This does not check + /// for 'CountIsNextParam' because that is only used in precision, not + /// width. + fn count(&mut self) -> Count { + match self.integer() { + Some(i) => { + if self.consume('$') { + CountIsParam(i) + } else { + CountIs(i) + } + } + None => { CountImplied } + } + } + + /// Parses a word starting at the current position. A word is considered to + /// be an alphabetic character followed by any number of alphanumeric + /// characters. + fn word(&mut self) -> &'self str { + let start = match self.cur.clone().next() { + Some((pos, c)) if char::is_alphabetic(c) => { + self.cur.next(); + pos + } + Some(*) | None => { return self.input.slice(0, 0); } + }; + let mut end; + loop { + match self.cur.clone().next() { + Some((_, c)) if char::is_alphanumeric(c) => { + self.cur.next(); + } + Some((pos, _)) => { end = pos; break } + None => { end = self.input.len(); break } + } + } + self.input.slice(start, end) + } + + /// Optionally parses an integer at the current position. This doesn't deal + /// with overflow at all, it's just accumulating digits. + fn integer(&mut self) -> Option { + let mut cur = 0; + let mut found = false; + loop { + match self.cur.clone().next() { + Some((_, c)) => { + match char::to_digit(c, 10) { + Some(i) => { + cur = cur * 10 + i; + found = true; + self.cur.next(); + } + None => { break } + } + } + None => { break } + } + } + if found { + return Some(cur); + } else { + return None; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use prelude::*; + use realstd::fmt::{String}; + + fn same(fmt: &'static str, p: ~[Piece<'static>]) { + let mut parser = Parser::new(fmt, |m| fail!("%s", m)); + assert_eq!(p, parser.collect()); + } + + fn fmtdflt() -> FormatSpec<'static> { + return FormatSpec { + fill: None, + align: None, + flags: 0, + precision: CountImplied, + width: CountImplied, + ty: "", + } + } + + fn musterr(s: &str) { + Parser::new(s, |m| fail!("%s", m)).next(); + } + + #[test] + fn simple() { + same("asdf", ~[String("asdf")]); + same("a\\{b", ~[String("a"), String("{b")]); + same("a\\#b", ~[String("a"), String("#b")]); + same("a\\}b", ~[String("a"), String("}b")]); + same("a\\}", ~[String("a"), String("}")]); + same("\\}", ~[String("}")]); + } + + #[test] #[should_fail] fn invalid01() { musterr("{") } + #[test] #[should_fail] fn invalid02() { musterr("\\") } + #[test] #[should_fail] fn invalid03() { musterr("\\a") } + #[test] #[should_fail] fn invalid04() { musterr("{3a}") } + #[test] #[should_fail] fn invalid05() { musterr("{:|}") } + #[test] #[should_fail] fn invalid06() { musterr("{:>>>}") } + + #[test] + fn format_nothing() { + same("{}", ~[Argument(Argument { + position: ArgumentNext, + format: fmtdflt(), + method: None, + })]); + } + #[test] + fn format_position() { + same("{3}", ~[Argument(Argument { + position: ArgumentIs(3), + format: fmtdflt(), + method: None, + })]); + } + #[test] + fn format_position_nothing_else() { + same("{3:}", ~[Argument(Argument { + position: ArgumentIs(3), + format: fmtdflt(), + method: None, + })]); + } + #[test] + fn format_type() { + same("{3:a}", ~[Argument(Argument { + position: ArgumentIs(3), + format: FormatSpec { + fill: None, + align: None, + flags: 0, + precision: CountImplied, + width: CountImplied, + ty: "a", + }, + method: None, + })]); + } + #[test] + fn format_align_fill() { + same("{3:>}", ~[Argument(Argument { + position: ArgumentIs(3), + format: FormatSpec { + fill: None, + align: Some(AlignRight), + flags: 0, + precision: CountImplied, + width: CountImplied, + ty: "", + }, + method: None, + })]); + same("{3:0<}", ~[Argument(Argument { + position: ArgumentIs(3), + format: FormatSpec { + fill: Some('0'), + align: Some(AlignLeft), + flags: 0, + precision: CountImplied, + width: CountImplied, + ty: "", + }, + method: None, + })]); + same("{3:* or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use prelude::*; + +use cast; +use libc; +use str; +use uint; +use util; +use vec; + +#[cfg(not(test))] +use super::{int, sys}; + +pub mod ct; + +/// A struct to represent both where to emit formatting strings to and how they +/// should be formatted. A mutable version of this is passed to all formatting +/// traits. +pub struct Formatter<'self> { + /// Flags for formatting (packed version of ct::Flag) + flags: uint, + /// Character used as 'fill' whenever there is alignment + fill: char, + /// Boolean indication of whether the output should be left-aligned + alignleft: bool, + /// Optionally specified integer width that the output should be + width: Option, + /// Optionally specified precision for numeric types + precision: Option, + + /// Output buffer. + // XXX: this should be '&mut io::Writer' when borrowed traits work better + buf: &'self mut ~str, + + priv curarg: vec::VecIterator<'self, Argument<'self>>, + priv args: &'self [Argument<'self>], + priv named_args: &'self [(&'self str, Argument<'self>)], +} + +/// This struct represents the generic "argument" which is taken by the Xprintf +/// family of functions. It contains a function to format the given value. At +/// compile time it is ensured that the function and the value have the correct +/// types, and then this struct is used to canonicalize arguments to one type. +pub struct Argument<'self> { + priv formatter: extern "Rust" fn(&util::Void, &mut Formatter), + priv value: &'self util::Void, +} + +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="b"] +pub trait Bool { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="c"] +pub trait Char { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="d"] +pub trait Signed { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="u"] #[fmt="i"] +pub trait Unsigned { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="o"] +pub trait Octal { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="t"] +pub trait Binary { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="x"] +pub trait LowerHex { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="X"] +pub trait UpperHex { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="s"] +pub trait String { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="?"] +pub trait Poly { fn fmt(&Self, &mut Formatter); } +#[allow(missing_doc)] #[cfg(not(test))] #[fmt="p"] +pub trait Pointer { fn fmt(&Self, &mut Formatter); } + +/// The sprintf function takes a format string, a list of arguments, a list of +/// named arguments, and it returns the resulting formatted string. +/// +/// This is currently an unsafe function because the types of all arguments +/// aren't verified by immediate callers of this function. This currently does +/// not validate that the correct types of arguments are specified for each +/// format specifier, nor that each argument itself contains the right function +/// for formatting the right type value. Because of this, the function is marked +/// as `unsafe` if this is being called manually. +/// +/// Thankfully the rust compiler provides the macro `ifmt!` which will perform +/// all of this validation at compile-time and provides a safe interface for +/// invoking this function. +/// +/// # Arguments +/// +/// * fmts - the format string to control output +/// * args - the list of arguments to the format string. These are only the +/// positional arguments (not named) +/// * named_args - this is a list of named arguments, the first value of the +/// pair is the name, and the second is the actual argument. +/// +/// Note that this function assumes that there are enough arguments and +/// named_args for the format string, and that each named arg in the format +/// string is also present in named_args. +pub unsafe fn sprintf(fmts: &str, args: &[Argument], + named_args: &[(&str, Argument)]) -> ~str { + let mut output = ~""; + { + let mut fmt = Formatter { + flags: 0, + width: None, + precision: None, + buf: &mut output, + alignleft: false, + fill: ' ', + curarg: args.iter(), + args: args, + named_args: named_args, + }; + + for ct::Parser::new(fmts, |_| libc::abort()).advance |piece| { + fmt.run(&piece, None); + } + } + return output; +} + +impl<'self> Formatter<'self> { + fn run(&mut self, piece: &ct::Piece<'self>, cur: Option<&str>) { + let setcount = |slot: &mut Option, cnt: &ct::Count| { + match *cnt { + ct::CountIs(n) => { *slot = Some(n); } + ct::CountImplied => { *slot = None; } + ct::CountIsParam(i) => { + let v = self.args[i].value; + unsafe { *slot = Some(*(v as *util::Void as *uint)); } + } + ct::CountIsNextParam => { + let v = self.curarg.next().unwrap().value; + unsafe { *slot = Some(*(v as *util::Void as *uint)); } + } + } + }; + + match *piece { + ct::String(s) => { self.buf.push_str(s); } + ct::CurrentArgument => { self.buf.push_str(cur.unwrap()); } + ct::Argument(ref arg) => { + // Fill in the format parameters into the formatter + match arg.format.fill { + Some(c) => { self.fill = c; } + None => { self.fill = ' '; } + } + match arg.format.align { + Some(ct::AlignRight) => { self.alignleft = false; } + Some(ct::AlignLeft) | None => { self.alignleft = true; } + } + self.flags = arg.format.flags; + setcount(&mut self.width, &arg.format.width); + setcount(&mut self.precision, &arg.format.precision); + + // Extract the correct argument + let value = match arg.position { + ct::ArgumentNext => { *self.curarg.next().unwrap() } + ct::ArgumentIs(i) => self.args[i], + ct::ArgumentNamed(name) => { + match self.named_args.iter().find_(|& &(s, _)| s == name){ + Some(&(_, arg)) => arg, + None => { + fail!(fmt!("No argument named `%s`", name)) + } + } + } + }; + + // Then actually do some printing + match arg.method { + None => { (value.formatter)(value.value, self); } + Some(ref method) => { self.execute(*method, value); } + } + } + } + } + + fn execute(&mut self, method: &ct::Method<'self>, arg: Argument) { + match *method { + // Pluralization is selection upon a numeric value specified as the + // parameter. + ct::Plural(offset, ref selectors, ref default) => { + // This is validated at compile-time to be a pointer to a + // '&uint' value. + let value: &uint = unsafe { cast::transmute(arg.value) }; + let value = *value; + + // First, attempt to match against explicit values without the + // offsetted value + for selectors.iter().advance |s| { + match s.selector { + Right(val) if value == val => { + return self.runplural(value, s.result); + } + _ => {} + } + } + + // Next, offset the value and attempt to match against the + // keyword selectors. + let value = value - offset.get_or_default(0); + for selectors.iter().advance |s| { + let run = match s.selector { + Left(ct::Zero) => value == 0, + Left(ct::One) => value == 1, + Left(ct::Two) => value == 2, + + // XXX: Few/Many should have a user-specified boundary + // One possible option would be in the function + // pointer of the 'arg: Argument' struct. + Left(ct::Few) => value < 8, + Left(ct::Many) => value >= 8, + + Right(*) => false + }; + if run { + return self.runplural(value, s.result); + } + } + + self.runplural(value, *default); + } + + // Select is just a matching against the string specified. + ct::Select(ref selectors, ref default) => { + // This is validated at compile-time to be a pointer to a + // string slice, + let value: & &str = unsafe { cast::transmute(arg.value) }; + let value = *value; + + for selectors.iter().advance |s| { + if s.selector == value { + for s.result.iter().advance |piece| { + self.run(piece, Some(value)); + } + return; + } + } + for default.iter().advance |piece| { + self.run(piece, Some(value)); + } + } + } + } + + fn runplural(&mut self, value: uint, pieces: &[ct::Piece<'self>]) { + do uint::to_str_bytes(value, 10) |buf| { + let valuestr = str::from_bytes_slice(buf); + for pieces.iter().advance |piece| { + self.run(piece, Some(valuestr)); + } + } + } +} + +/// This is a function which calls are emitted to by the compiler itself to +/// create the Argument structures that are passed into the `sprintf` function. +#[doc(hidden)] +pub fn argument<'a, T>(f: extern "Rust" fn(&T, &mut Formatter), + t: &'a T) -> Argument<'a> { + unsafe { + Argument { + formatter: cast::transmute(f), + value: cast::transmute(t) + } + } +} + +/// When the compiler determines that the type of an argument *must* be a string +/// (such as for select), then it invokes this method. +#[doc(hidden)] +pub fn argumentstr<'a>(s: &'a &str) -> Argument<'a> { + argument(String::fmt, s) +} + +/// When the compiler determines that the type of an argument *must* be a uint +/// (such as for plural), then it invokes this method. +#[doc(hidden)] +pub fn argumentuint<'a>(s: &'a uint) -> Argument<'a> { + argument(Unsigned::fmt, s) +} + +// Implementations of the core formatting traits + +#[cfg(not(test))] +impl Bool for bool { + fn fmt(b: &bool, f: &mut Formatter) { + String::fmt(&(if *b {"true"} else {"false"}), f); + } +} + +#[cfg(not(test))] +impl<'self> String for &'self str { + fn fmt(s: & &'self str, f: &mut Formatter) { + // XXX: formatting args + f.buf.push_str(*s) + } +} + +#[cfg(not(test))] +impl Char for char { + fn fmt(c: &char, f: &mut Formatter) { + // XXX: formatting args + f.buf.push_char(*c); + } +} + +#[cfg(not(test))] +impl Signed for int { + fn fmt(c: &int, f: &mut Formatter) { + // XXX: formatting args + do int::to_str_bytes(*c, 10) |buf| { + f.buf.push_str(str::from_bytes_slice(buf)); + } + } +} + +#[cfg(not(test))] +impl Unsigned for uint { + fn fmt(c: &uint, f: &mut Formatter) { + // XXX: formatting args + do uint::to_str_bytes(*c, 10) |buf| { + f.buf.push_str(str::from_bytes_slice(buf)); + } + } +} + +#[cfg(not(test))] +impl Octal for uint { + fn fmt(c: &uint, f: &mut Formatter) { + // XXX: formatting args + do uint::to_str_bytes(*c, 8) |buf| { + f.buf.push_str(str::from_bytes_slice(buf)); + } + } +} + +#[cfg(not(test))] +impl LowerHex for uint { + fn fmt(c: &uint, f: &mut Formatter) { + // XXX: formatting args + do uint::to_str_bytes(*c, 16) |buf| { + f.buf.push_str(str::from_bytes_slice(buf)); + } + } +} + +#[cfg(not(test))] +impl UpperHex for uint { + fn fmt(c: &uint, f: &mut Formatter) { + // XXX: formatting args + do uint::to_str_bytes(*c, 16) |buf| { + let mut local = [0u8, ..16]; + for local.mut_iter().zip(buf.iter()).advance |(l, &b)| { + *l = match b as char { + 'a' .. 'f' => (b - 'a' as u8) + 'A' as u8, + _ => b, + }; + } + f.buf.push_str(str::from_bytes_slice(local.slice_to(buf.len()))); + } + } +} + +#[cfg(not(test))] +impl Poly for T { + fn fmt(t: &T, f: &mut Formatter) { + // XXX: formatting args + f.buf.push_str(sys::log_str(t)); + } +} + +// n.b. use 'const' to get an implementation for both '*mut' and '*' at the same +// time. +#[cfg(not(test))] +impl Pointer for *const T { + fn fmt(t: &*const T, f: &mut Formatter) { + // XXX: formatting args + f.buf.push_str("0x"); + LowerHex::fmt(&(*t as uint), f); + } +} + +#[cfg(test)] +mod tests { + use realstd::fmt; + + use c = realstd::fmt::argument; + + fn run(s: &str, args: &[fmt::Argument]) -> ~str { + unsafe { fmt::sprintf(s, args, []) } + } + + #[test] + fn simple() { + assert_eq!(run("hello", []), ~"hello"); + assert_eq!(run("", []), ~""); + assert_eq!(run("\\#", []), ~"#"); + assert_eq!(run("a\\#b", []), ~"a#b"); + } + + #[test] + fn simple_args() { + let a: uint = 1; + let b: int = 2; + let s = "hello"; + assert_eq!(run("a{}b", [c(fmt::Unsigned::fmt, &a)]), ~"a1b"); + assert_eq!(run("{} yay", [c(fmt::Signed::fmt, &b)]), ~"2 yay"); + assert_eq!(run("{}", [c(fmt::String::fmt, &s)]), ~"hello"); + assert_eq!(run("{} {2} {} {1} {}", + [c(fmt::String::fmt, &s), + c(fmt::Unsigned::fmt, &a), + c(fmt::Signed::fmt, &b)]), + ~"hello 2 1 1 2"); + } + + #[test] + fn plural_method() { + let one = 1u; + let one = c(fmt::Unsigned::fmt, &one); + let two = 2u; + let two = c(fmt::Unsigned::fmt, &two); + let three = 3u; + let three = c(fmt::Unsigned::fmt, &three); + let zero = 0u; + let zero = c(fmt::Unsigned::fmt, &zero); + let six = 6u; + let six = c(fmt::Unsigned::fmt, &six); + let ten = 10u; + let ten = c(fmt::Unsigned::fmt, &ten); + + assert_eq!(run("{0,plural, other{a}}", [one]), ~"a"); + assert_eq!(run("{0,plural, =1{b} other{a}}", [one]), ~"b"); + assert_eq!(run("{0,plural, =0{b} other{a}}", [one]), ~"a"); + assert_eq!(run("{0,plural, =1{a} one{b} other{c}}", [one]), ~"a"); + assert_eq!(run("{0,plural, one{b} other{c}}", [one]), ~"b"); + assert_eq!(run("{0,plural, two{b} other{c}}", [two]), ~"b"); + assert_eq!(run("{0,plural, zero{b} other{c}}", [zero]), ~"b"); + assert_eq!(run("{0,plural, offset:1 zero{b} other{c}}", [zero]), ~"c"); + assert_eq!(run("{0,plural, offset:1 zero{b} other{c}}", [one]), ~"b"); + assert_eq!(run("{0,plural, few{a} many{b} other{c}}", [three]), ~"a"); + assert_eq!(run("{0,plural, few{a} many{b} other{c}}", [six]), ~"a"); + assert_eq!(run("{0,plural, few{a} many{b} other{c}}", [ten]), ~"b"); + assert_eq!(run("{0,plural, few{a} other{c}}", [ten]), ~"c"); + assert_eq!(run("{0,plural, few{a} other{#}}", [ten]), ~"10"); + } + + #[test] + fn select_method() { + let a = "a"; + let b = "b"; + let a = c(fmt::String::fmt, &a); + let b = c(fmt::String::fmt, &b); + + assert_eq!(run("{0,select, other{a}}", [a]), ~"a"); + assert_eq!(run("{0,select, a{a} other{b}}", [a]), ~"a"); + assert_eq!(run("{0,select, a{#} other{b}}", [a]), ~"a"); + assert_eq!(run("{0,select, a{a} other{b}}", [b]), ~"b"); + assert_eq!(run("{0,select, a{s} b{t} other{b}}", [b]), ~"t"); + assert_eq!(run("{0,select, a{s} b{t} other{b}}", [a]), ~"s"); + } + + #[test] + fn nested_method() { + let a = "a"; + let a = c(fmt::String::fmt, &a); + + assert_eq!(run("{0,select,other{{0,select,a{a} other{b}}}}", [a]), ~"a"); + assert_eq!(run("{0,select,other{{0,select,c{a} other{b}}}}", [a]), ~"b"); + assert_eq!(run("{0,select,other{#{0,select,c{a} other{b}}}}", [a]), ~"ab"); + } +} diff --git a/src/libstd/std.rs b/src/libstd/std.rs index 76d65192e01a9..d489cc0b8eee4 100644 --- a/src/libstd/std.rs +++ b/src/libstd/std.rs @@ -178,6 +178,7 @@ pub mod rand; pub mod run; pub mod sys; pub mod cast; +pub mod fmt; pub mod repr; pub mod cleanup; pub mod reflect; @@ -218,4 +219,6 @@ mod std { pub use unstable; pub use str; pub use os; + pub use fmt; + pub use to_bytes; } diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 97e69cd69184a..cad575ffbf92e 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -504,7 +504,10 @@ pub enum expr_ { expr_repeat(@expr /* element */, @expr /* count */, mutability), // No-op: used solely so we can pretty-print faithfully - expr_paren(@expr) + expr_paren(@expr), + + // Internal representation of the formatter of an argument to fmt! + expr_extfmt_fn(@str), } // When the main rust parser encounters a syntax-extension invocation, it diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index ea87646e60b0b..0c01a6e2abc14 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -139,6 +139,8 @@ pub fn syntax_expander_table() -> SyntaxEnv { ext::tt::macro_rules::add_new_extension)); syntax_expanders.insert(intern(&"fmt"), builtin_normal_tt(ext::fmt::expand_syntax_ext)); + syntax_expanders.insert(intern(&"ifmt"), + builtin_normal_tt(ext::ifmt::expand_syntax_ext)); syntax_expanders.insert( intern(&"auto_encode"), @SE(ItemDecorator(ext::auto_encode::expand_auto_encode))); diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index af05f72686060..4e7cd8c98d643 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -742,7 +742,9 @@ pub fn expand_crate(parse_sess: @mut parse::ParseSess, .. *afp}; let f = make_fold(f_pre); - @f.fold_crate(c) + let ret = @f.fold_crate(c); + parse_sess.span_diagnostic.handler().abort_if_errors(); + return ret; } // given a function from idents to idents, produce diff --git a/src/libsyntax/ext/ifmt.rs b/src/libsyntax/ext/ifmt.rs new file mode 100644 index 0000000000000..4a21368fed2c8 --- /dev/null +++ b/src/libsyntax/ext/ifmt.rs @@ -0,0 +1,433 @@ +// Copyright 2012 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use ast; +use codemap::span; +use ext::base::*; +use ext::base; +use ext::build::AstBuilder; +use parse; +use parse::token; + +use std::fmt::ct; +use std::hashmap::{HashMap, HashSet}; + +#[deriving(Eq)] +enum ArgumentType { + Unknown, + Known(@str), + Unsigned, + String, +} + +struct Context { + ecx: @ExtCtxt, + fmtsp: span, + + // Parsed argument expressions and the types that we've found so far for + // them. + args: ~[@ast::expr], + arg_types: ~[Option], + // Parsed named expressions and the types that we've found for them so far + names: HashMap<@str, @ast::expr>, + name_types: HashMap<@str, ArgumentType>, + + // Updated as arguments are consumed or methods are entered + nest_level: uint, + next_arg: uint, +} + +impl Context { + /// Parses the arguments from the given list of tokens, returning None if + /// there's a parse error so we can continue parsing other fmt! expressions. + fn parse_args(&mut self, sp: span, + tts: &[ast::token_tree]) -> Option<@ast::expr> { + let p = parse::new_parser_from_tts(self.ecx.parse_sess(), + self.ecx.cfg(), + tts.to_owned()); + if *p.token == token::EOF { + self.ecx.span_err(sp, "ifmt! expects at least one argument"); + return None; + } + let fmtstr = p.parse_expr(); + let mut named = false; + while *p.token != token::EOF { + if !p.eat(&token::COMMA) { + self.ecx.span_err(sp, "expected token: `,`"); + return None; + } + if named || (token::is_ident(p.token) && + p.look_ahead(1, |t| *t == token::EQ)) { + named = true; + let ident = match *p.token { + token::IDENT(i, _) => { + p.bump(); + i + } + _ if named => { + self.ecx.span_err(*p.span, + "expected ident, positional arguments \ + cannot follow named arguments"); + return None; + } + _ => { + self.ecx.span_err(*p.span, + fmt!("expected ident for named \ + argument, but found `%s`", + p.this_token_to_str())); + return None; + } + }; + let name = self.ecx.str_of(ident); + p.expect(&token::EQ); + let e = p.parse_expr(); + match self.names.find(&name) { + None => {} + Some(prev) => { + self.ecx.span_err(e.span, fmt!("duplicate argument \ + named `%s`", name)); + self.ecx.parse_sess.span_diagnostic.span_note( + prev.span, "previously here"); + loop + } + } + self.names.insert(name, e); + } else { + self.args.push(p.parse_expr()); + self.arg_types.push(None); + } + } + return Some(fmtstr); + } + + /// Verifies one piece of a parse string. All errors are not emitted as + /// fatal so we can continue giving errors about this and possibly other + /// format strings. + fn verify_piece(&mut self, p: &ct::Piece) { + match *p { + ct::String(*) => {} + ct::CurrentArgument => { + if self.nest_level == 0 { + self.ecx.span_err(self.fmtsp, + "`#` reference used with nothing to \ + reference back to"); + } + } + ct::Argument(ref arg) => { + // argument first (it's first in the format string) + let pos = match arg.position { + ct::ArgumentNext => { + let i = self.next_arg; + self.next_arg += 1; + Left(i) + } + ct::ArgumentIs(i) => Left(i), + ct::ArgumentNamed(s) => Right(s.to_managed()), + }; + let ty = if arg.format.ty == "" { + Unknown + } else { Known(arg.format.ty.to_managed()) }; + self.verify_arg_type(pos, ty); + + // width/precision next + self.verify_count(arg.format.width); + self.verify_count(arg.format.precision); + + // and finally the method being applied + match arg.method { + None => {} + Some(ref method) => { self.verify_method(pos, *method); } + } + } + } + } + + fn verify_pieces(&mut self, pieces: &[ct::Piece]) { + for pieces.iter().advance |piece| { + self.verify_piece(piece); + } + } + + fn verify_count(&mut self, c: ct::Count) { + match c { + ct::CountImplied | ct::CountIs(*) => {} + ct::CountIsNextParam => { + self.verify_arg_type(Left(self.next_arg), Unsigned); + self.next_arg += 1; + } + ct::CountIsParam(i) => { + self.verify_arg_type(Left(i), Unsigned); + } + } + } + + fn verify_method(&mut self, pos: Either, m: &ct::Method) { + self.nest_level += 1; + match *m { + ct::Plural(_, ref arms, ref default) => { + let mut seen_cases = HashSet::new(); + self.verify_arg_type(pos, Unsigned); + for arms.iter().advance |arm| { + if !seen_cases.insert(arm.selector) { + match arm.selector { + Left(name) => { + self.ecx.span_err(self.fmtsp, + fmt!("duplicate selector \ + `%?`", name)); + } + Right(idx) => { + self.ecx.span_err(self.fmtsp, + fmt!("duplicate selector \ + `=%u`", idx)); + } + } + } + self.verify_pieces(arm.result); + } + self.verify_pieces(*default); + } + ct::Select(ref arms, ref default) => { + self.verify_arg_type(pos, String); + let mut seen_cases = HashSet::new(); + for arms.iter().advance |arm| { + if !seen_cases.insert(arm.selector) { + self.ecx.span_err(self.fmtsp, + fmt!("duplicate selector `%s`", + arm.selector)); + } else if arm.selector == "" { + self.ecx.span_err(self.fmtsp, + "empty selector in `select`"); + } + self.verify_pieces(arm.result); + } + self.verify_pieces(*default); + } + } + self.nest_level -= 1; + } + + fn verify_arg_type(&mut self, arg: Either, ty: ArgumentType) { + match arg { + Left(arg) => { + if arg < 0 || self.args.len() <= arg { + let msg = fmt!("invalid reference to argument `%u` (there \ + are %u arguments)", arg, self.args.len()); + self.ecx.span_err(self.fmtsp, msg); + return; + } + self.verify_same(self.args[arg].span, ty, self.arg_types[arg]); + if ty != Unknown || self.arg_types[arg].is_none() { + self.arg_types[arg] = Some(ty); + } + } + + Right(name) => { + let span = match self.names.find(&name) { + Some(e) => e.span, + None => { + let msg = fmt!("There is no argument named `%s`", name); + self.ecx.span_err(self.fmtsp, msg); + return; + } + }; + self.verify_same(span, ty, + self.name_types.find(&name).map(|&x| *x)); + if ty != Unknown || !self.name_types.contains_key(&name) { + self.name_types.insert(name, ty); + } + } + } + } + + /// When we're keeping track of the types that are declared for certain + /// arguments, we assume that `None` means we haven't seen this argument + /// yet, `Some(None)` means that we've seen the argument, but no format was + /// specified, and `Some(Some(x))` means that the argument was declared to + /// have type `x`. + /// + /// Obviously `Some(Some(x)) != Some(Some(y))`, but we consider it true + /// that: `Some(None) == Some(Some(x))` + fn verify_same(&self, sp: span, ty: ArgumentType, + before: Option) { + if ty == Unknown { return } + let cur = match before { + Some(Unknown) | None => return, + Some(t) => t, + }; + if ty == cur { return } + match (cur, ty) { + (Known(cur), Known(ty)) => { + self.ecx.span_err(sp, + fmt!("argument redeclared with type `%s` when \ + it was previously `%s`", ty, cur)); + } + (Known(cur), _) => { + self.ecx.span_err(sp, + fmt!("argument used to format with `%s` was \ + attempted to not be used for formatting", + cur)); + } + (_, Known(ty)) => { + self.ecx.span_err(sp, + fmt!("argument previously used as a format \ + argument attempted to be used as `%s`", + ty)); + } + (_, _) => { + self.ecx.span_err(sp, "argument declared with multiple formats"); + } + } + } + + /// Actually builds the expression which the ifmt! block will be expanded + /// to + fn to_expr(&self, fmtstr: @ast::expr) -> @ast::expr { + let mut lets = ~[]; + let mut locals = ~[]; + let mut names = ~[]; + + // Right now there is a bug such that for the expression: + // foo(bar(&1)) + // the lifetime of `1` doesn't outlast the call to `bar`, so it's not + // vald for the call to `foo`. To work around this all arguments to the + // fmt! string are shoved into locals. + for self.args.iter().enumerate().advance |(i, &e)| { + if self.arg_types[i].is_none() { loop } // error already generated + + let name = self.ecx.ident_of(fmt!("__arg%u", i)); + lets.push(self.ecx.stmt_let(e.span, false, name, e)); + locals.push(self.format_arg(e.span, Left(i), name)); + } + for self.names.iter().advance |(&name, &e)| { + if !self.name_types.contains_key(&name) { loop } + + let lname = self.ecx.ident_of(fmt!("__arg%s", name)); + lets.push(self.ecx.stmt_let(e.span, false, lname, e)); + let tup = ~[self.ecx.expr_str(e.span, name), + self.format_arg(e.span, Right(name), lname)]; + names.push(self.ecx.expr(e.span, ast::expr_tup(tup))); + } + + // Next, build up the actual call to the sprintf function. This takes + // three arguments: + // 1. The format string + // 2. An array of arguments + // 3. An array of (name, argument) pairs + let result = self.ecx.expr_call_global(self.fmtsp, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("fmt"), + self.ecx.ident_of("sprintf"), + ], ~[ + fmtstr, + self.ecx.expr_vec(fmtstr.span, locals), + self.ecx.expr_vec(fmtstr.span, names) + ]); + + // sprintf is unsafe, but we just went through a lot of work to + // validate that our call is save, so inject the unsafe block for the + // user. + let result = self.ecx.expr_blk(ast::Block { + view_items: ~[], + stmts: ~[], + expr: Some(result), + id: self.ecx.next_id(), + rules: ast::UnsafeBlock, + span: self.fmtsp, + }); + + self.ecx.expr_blk(self.ecx.blk(self.fmtsp, lets, Some(result))) + } + + fn format_arg(&self, sp: span, arg: Either, + ident: ast::ident) -> @ast::expr { + let mut ty = match arg { + Left(i) => self.arg_types[i].unwrap(), + Right(s) => *self.name_types.get(&s) + }; + // Default types to '?' if nothing else is specified. + if ty == Unknown { + ty = Known(@"?"); + } + let argptr = self.ecx.expr_addr_of(sp, self.ecx.expr_ident(sp, ident)); + match ty { + Known(tyname) => { + let format_fn = self.ecx.expr(sp, ast::expr_extfmt_fn(tyname)); + self.ecx.expr_call_global(sp, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("fmt"), + self.ecx.ident_of("argument"), + ], ~[format_fn, argptr]) + } + String => { + self.ecx.expr_call_global(sp, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("fmt"), + self.ecx.ident_of("argumentstr"), + ], ~[argptr]) + } + Unsigned => { + self.ecx.expr_call_global(sp, ~[ + self.ecx.ident_of("std"), + self.ecx.ident_of("fmt"), + self.ecx.ident_of("argumentuint"), + ], ~[argptr]) + } + Unknown => { fail!() } + } + } +} + +pub fn expand_syntax_ext(ecx: @ExtCtxt, sp: span, + tts: &[ast::token_tree]) -> base::MacResult { + let mut cx = Context { + ecx: ecx, + args: ~[], + arg_types: ~[], + names: HashMap::new(), + name_types: HashMap::new(), + nest_level: 0, + next_arg: 0, + fmtsp: sp, + }; + let efmt = match cx.parse_args(sp, tts) { + Some(e) => e, + None => { return MRExpr(ecx.expr_uint(sp, 2)); } + }; + cx.fmtsp = efmt.span; + let fmt = expr_to_str(ecx, efmt, + ~"first argument to ifmt! must be a string literal."); + + let mut err = false; + for ct::Parser::new(fmt, |m| { + if !err { + err = true; + ecx.span_err(efmt.span, m); + } + }).advance |piece| { + if !err { + cx.verify_piece(&piece); + } + } + if err { return MRExpr(efmt); } + + // Make sure that all arguments were used and all arguments have types. + for cx.arg_types.iter().enumerate().advance |(i, ty)| { + if ty.is_none() { + ecx.span_err(cx.args[i].span, "argument never used"); + } + } + for cx.names.iter().advance |(name, e)| { + if !cx.name_types.contains_key(name) { + ecx.span_err(e.span, "named argument never used"); + } + } + + MRExpr(cx.to_expr(efmt)) +} diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs index 51475528174f4..e19b2c63158e0 100644 --- a/src/libsyntax/fold.rs +++ b/src/libsyntax/fold.rs @@ -634,7 +634,8 @@ pub fn noop_fold_expr(e: &expr_, fld: @ast_fold) -> expr_ { maybe_expr.map(|x| fld.fold_expr(*x)) ) }, - expr_paren(ex) => expr_paren(fld.fold_expr(ex)) + expr_paren(ex) => expr_paren(fld.fold_expr(ex)), + expr_extfmt_fn(s) => expr_extfmt_fn(s), } } diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index 7083d6655f1d0..fdfc635eeb46b 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -1442,6 +1442,11 @@ pub fn print_expr(s: @ps, expr: &ast::expr) { print_expr(s, e); pclose(s); } + ast::expr_extfmt_fn(ty) => { + word(s.s, "FormatTrait{"); + word(s.s, ty); + word(s.s, "}"); + } } (s.ann.post)(ann_node); end(s); diff --git a/src/libsyntax/syntax.rs b/src/libsyntax/syntax.rs index ae2aa6ae73891..7e64fbaae574d 100644 --- a/src/libsyntax/syntax.rs +++ b/src/libsyntax/syntax.rs @@ -71,6 +71,7 @@ pub mod ext { pub mod fmt; + pub mod ifmt; pub mod env; pub mod bytes; pub mod concat_idents; diff --git a/src/libsyntax/visit.rs b/src/libsyntax/visit.rs index 1d264bcc20ece..9222c73039ffb 100644 --- a/src/libsyntax/visit.rs +++ b/src/libsyntax/visit.rs @@ -565,6 +565,7 @@ pub fn visit_expr(ex: @expr, (e, v): (E, vt)) { (v.visit_expr)(out, (e.clone(), v)); } } + expr_extfmt_fn(*) => {} } (v.visit_expr_post)(ex, (e, v)); } diff --git a/src/test/auxiliary/ifmt_bad_trait1.rs b/src/test/auxiliary/ifmt_bad_trait1.rs new file mode 100644 index 0000000000000..6dfa00c7795d4 --- /dev/null +++ b/src/test/auxiliary/ifmt_bad_trait1.rs @@ -0,0 +1,14 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt::Formatter; + +#[fmt="external"] +trait C { fn fmt(&Self, &mut Formatter); } diff --git a/src/test/auxiliary/ifmt_bad_trait2.rs b/src/test/auxiliary/ifmt_bad_trait2.rs new file mode 100644 index 0000000000000..6dfa00c7795d4 --- /dev/null +++ b/src/test/auxiliary/ifmt_bad_trait2.rs @@ -0,0 +1,14 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt::Formatter; + +#[fmt="external"] +trait C { fn fmt(&Self, &mut Formatter); } diff --git a/src/test/compile-fail/ifmt-bad-arg.rs b/src/test/compile-fail/ifmt-bad-arg.rs new file mode 100644 index 0000000000000..d788a9b962ac5 --- /dev/null +++ b/src/test/compile-fail/ifmt-bad-arg.rs @@ -0,0 +1,66 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + // bad arguments to the ifmt! call + + ifmt!(); //~ ERROR: expects at least one + ifmt!("{}"); //~ ERROR: invalid reference to argument + + ifmt!("{1}", 1); //~ ERROR: invalid reference to argument `1` + //~^ ERROR: argument never used + ifmt!("{foo}"); //~ ERROR: no argument named `foo` + + ifmt!("{}", 1, 2); //~ ERROR: argument never used + ifmt!("{1}", 1, 2); //~ ERROR: argument never used + ifmt!("{}", 1, foo=2); //~ ERROR: named argument never used + ifmt!("{foo}", 1, foo=2); //~ ERROR: argument never used + ifmt!("", foo=2); //~ ERROR: named argument never used + + ifmt!("{0:d} {0:s}", 1); //~ ERROR: redeclared with type `s` + ifmt!("{foo:d} {foo:s}", foo=1); //~ ERROR: redeclared with type `s` + + ifmt!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument + ifmt!("#"); //~ ERROR: `#` reference used + ifmt!("", foo=1, 2); //~ ERROR: positional arguments cannot follow + ifmt!("" 1); //~ ERROR: expected token: `,` + ifmt!("", 1 1); //~ ERROR: expected token: `,` + + ifmt!("{0, select, a{} a{} other{}}", "a"); //~ ERROR: duplicate selector + ifmt!("{0, plural, =1{} =1{} other{}}", 1u); //~ ERROR: duplicate selector + ifmt!("{0, plural, one{} one{} other{}}", 1u); //~ ERROR: duplicate selector + + // bad syntax of the format string + + ifmt!("{"); //~ ERROR: unterminated format string + ifmt!("\\ "); //~ ERROR: invalid escape + ifmt!("\\"); //~ ERROR: expected an escape + + ifmt!("{0, }", 1); //~ ERROR: expected method + ifmt!("{0, foo}", 1); //~ ERROR: unknown method + ifmt!("{0, select}", "a"); //~ ERROR: must be followed by + ifmt!("{0, plural}", 1); //~ ERROR: must be followed by + + ifmt!("{0, select, a{{}", 1); //~ ERROR: must be terminated + ifmt!("{0, select, {} other{}}", "a"); //~ ERROR: empty selector + ifmt!("{0, select, other{} other{}}", "a"); //~ ERROR: multiple `other` + ifmt!("{0, plural, offset: other{}}", "a"); //~ ERROR: must be an integer + ifmt!("{0, plural, offset 1 other{}}", "a"); //~ ERROR: be followed by `:` + ifmt!("{0, plural, =a{} other{}}", "a"); //~ ERROR: followed by an integer + ifmt!("{0, plural, a{} other{}}", "a"); //~ ERROR: unexpected plural + ifmt!("{0, select, a{}}", "a"); //~ ERROR: must provide an `other` + ifmt!("{0, plural, =1{}}", "a"); //~ ERROR: must provide an `other` + + ifmt!("{0, plural, other{{0:s}}}", "a"); //~ ERROR: previously used as + ifmt!("{:s} {0, plural, other{}}", "a"); //~ ERROR: argument used to + ifmt!("{0, select, other{}} \ + {0, plural, other{}}", "a"); + //~^ ERROR: declared with multiple formats +} diff --git a/src/test/compile-fail/ifmt-bad-plural.rs b/src/test/compile-fail/ifmt-bad-plural.rs new file mode 100644 index 0000000000000..76a697b174f54 --- /dev/null +++ b/src/test/compile-fail/ifmt-bad-plural.rs @@ -0,0 +1,14 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + ifmt!("{0, plural, other{}}", "a"); + //~^ ERROR: expected uint but found +} diff --git a/src/test/compile-fail/ifmt-bad-select.rs b/src/test/compile-fail/ifmt-bad-select.rs new file mode 100644 index 0000000000000..abe3b6ed65a6d --- /dev/null +++ b/src/test/compile-fail/ifmt-bad-select.rs @@ -0,0 +1,14 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + ifmt!("{0, select, other{}}", 2); + //~^ ERROR: expected &str but found integral +} diff --git a/src/test/compile-fail/ifmt-bad-trait.rs b/src/test/compile-fail/ifmt-bad-trait.rs new file mode 100644 index 0000000000000..59c50a9fa8333 --- /dev/null +++ b/src/test/compile-fail/ifmt-bad-trait.rs @@ -0,0 +1,43 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:ifmt_bad_trait1.rs +// aux-build:ifmt_bad_trait2.rs + +extern mod ifmt_bad_trait1; +extern mod ifmt_bad_trait2; + +use std::fmt::Formatter; + +#[fmt="fmt1"] +trait A {} //~ ERROR: no function named `fmt` + +#[fmt] //~ ERROR: fmt attribute must have a value specified +trait B {} + +#[fmt="a"] //~ ERROR: fmt attribute can only be specified on traits +fn a() {} + +#[fmt="fmt2"] +trait C { fn fmt(&Self, &mut Formatter); } +//~^ NOTE: previous definition here +#[fmt="fmt2"] +trait D { fn fmt(&Self, &mut Formatter); } +//~^ ERROR: duplicate fmt trait for `fmt2` + +#[fmt="d"] +trait E { fn fmt(&Self, &mut Formatter); } +//~^ ERROR: duplicate fmt trait for `d` + +#[fmt="fmt3"] +trait F { unsafe fn fmt(&Self, &mut Formatter); } +//~^ ERROR: the `fmt` function must not be unsafe + +fn main() {} diff --git a/src/test/compile-fail/ifmt-unimpl.rs b/src/test/compile-fail/ifmt-unimpl.rs new file mode 100644 index 0000000000000..427f5ea562c7e --- /dev/null +++ b/src/test/compile-fail/ifmt-unimpl.rs @@ -0,0 +1,14 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + ifmt!("{:d}", "3"); + //~^ ERROR: failed to find an implementation of trait std::fmt::Signed +} diff --git a/src/test/compile-fail/ifmt-unknown-trait.rs b/src/test/compile-fail/ifmt-unknown-trait.rs new file mode 100644 index 0000000000000..85556f9501acb --- /dev/null +++ b/src/test/compile-fail/ifmt-unknown-trait.rs @@ -0,0 +1,14 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + ifmt!("{:notimplemented}", "3"); + //~^ ERROR: unknown format trait `notimplemented` +} diff --git a/src/test/run-pass/ifmt.rs b/src/test/run-pass/ifmt.rs new file mode 100644 index 0000000000000..544b411ccdab0 --- /dev/null +++ b/src/test/run-pass/ifmt.rs @@ -0,0 +1,93 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::fmt::Formatter; + +struct A; +struct B; + +#[fmt="foo"] +trait Foo { + fn fmt(&Self, &mut Formatter); +} +impl Foo for A { + fn fmt(_: &A, f: &mut Formatter) { f.buf.push_str("aloha"); } +} +impl Foo for B { + fn fmt(_: &B, f: &mut Formatter) { f.buf.push_str("adios"); } +} + +#[fmt="bar"] +trait Bar { + #[allow(unused_variable)] // FIXME(#8148) + fn fmt(b: &Self, f: &mut Formatter) { f.buf.push_str("default"); } +} +impl Bar for A {} +impl Bar for B {} + +#[fmt="π"] +trait Snowman { + fn fmt(&Self, &mut Formatter); +} +impl Snowman for A { + fn fmt(_: &A, f: &mut Formatter) { f.buf.push_str("☃"); } +} + +pub fn main() { + fn t(a: ~str, b: &str) { assert_eq!(a, b.to_owned()); } + + // Make sure there's a poly formatter that takes anything + t(ifmt!("{}", 1), "1"); + t(ifmt!("{}", A), "{}"); + t(ifmt!("{}", ()), "()"); + t(ifmt!("{}", @(~1, "foo")), "@(~1, \"foo\")"); + + // Various edge cases without formats + t(ifmt!(""), ""); + t(ifmt!("hello"), "hello"); + t(ifmt!("hello \\{"), "hello {"); + + // At least exercise all the formats + t(ifmt!("{:b}", true), "true"); + t(ifmt!("{:c}", '☃'), "☃"); + t(ifmt!("{:d}", 10), "10"); + t(ifmt!("{:i}", 10u), "10"); + t(ifmt!("{:u}", 10u), "10"); + t(ifmt!("{:o}", 10u), "12"); + t(ifmt!("{:x}", 10u), "a"); + t(ifmt!("{:X}", 10u), "A"); + t(ifmt!("{:s}", "foo"), "foo"); + t(ifmt!("{:p}", 0x1234 as *int), "0x1234"); + t(ifmt!("{:p}", 0x1234 as *mut int), "0x1234"); + t(ifmt!("{:foo}", A), "aloha"); + t(ifmt!("{:foo}", B), "adios"); + t(ifmt!("{:π}", A), "☃"); + t(ifmt!("{:bar}", A), "default"); + t(ifmt!("{:bar}", B), "default"); + t(ifmt!("foo {:s} ☃☃☃☃☃☃", "bar"), "foo bar ☃☃☃☃☃☃"); + t(ifmt!("{1} {0}", 0, 1), "1 0"); + t(ifmt!("{foo} {bar}", foo=0, bar=1), "0 1"); + t(ifmt!("{foo} {1} {bar} {0}", 0, 1, foo=2, bar=3), "2 1 3 0"); + t(ifmt!("{} {0:s}", "a"), "a a"); + t(ifmt!("{} {0}", "a"), "\"a\" \"a\""); + + // Methods should probably work + t(ifmt!("{0, plural, =1{a#} =2{b#} zero{c#} other{d#}}", 0u), "c0"); + t(ifmt!("{0, plural, =1{a#} =2{b#} zero{c#} other{d#}}", 1u), "a1"); + t(ifmt!("{0, plural, =1{a#} =2{b#} zero{c#} other{d#}}", 2u), "b2"); + t(ifmt!("{0, plural, =1{a#} =2{b#} zero{c#} other{d#}}", 3u), "d3"); + t(ifmt!("{0, select, a{a#} b{b#} c{c#} other{d#}}", "a"), "aa"); + t(ifmt!("{0, select, a{a#} b{b#} c{c#} other{d#}}", "b"), "bb"); + t(ifmt!("{0, select, a{a#} b{b#} c{c#} other{d#}}", "c"), "cc"); + t(ifmt!("{0, select, a{a#} b{b#} c{c#} other{d#}}", "d"), "dd"); + t(ifmt!("{1, select, a{#{0:s}} other{#{1}}}", "b", "a"), "ab"); + t(ifmt!("{1, select, a{#{0}} other{#{1}}}", "c", "b"), "bb"); +} +