// 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. /* * The compiler code necessary to support the fmt! extension. Eventually this * should all get sucked into either the standard library extfmt module or the * compiler syntax extension plugin interface. */ use core::prelude::*; use ast; use codemap::span; use ext::base::*; use ext::base; use ext::build; use ext::build::*; use core::unstable::extfmt::ct::*; pub fn expand_syntax_ext(cx: @ext_ctxt, sp: span, tts: &[ast::token_tree]) -> base::MacResult { let args = get_exprs_from_tts(cx, tts); if args.len() == 0 { cx.span_fatal(sp, "fmt! takes at least 1 argument."); } let fmt = expr_to_str(cx, args[0], ~"first argument to fmt! must be a string literal."); let fmtspan = args[0].span; debug!("Format string: %s", fmt); fn parse_fmt_err_(cx: @ext_ctxt, sp: span, msg: &str) -> ! { cx.span_fatal(sp, msg); } let parse_fmt_err: @fn(&str) -> ! = |s| parse_fmt_err_(cx, fmtspan, s); let pieces = parse_fmt_string(fmt, parse_fmt_err); MRExpr(pieces_to_expr(cx, sp, pieces, args)) } // FIXME (#2249): A lot of these functions for producing expressions can // probably be factored out in common with other code that builds // expressions. Also: Cleanup the naming of these functions. // Note: Moved many of the common ones to build.rs --kevina fn pieces_to_expr(cx: @ext_ctxt, sp: span, pieces: ~[Piece], args: ~[@ast::expr]) -> @ast::expr { fn make_path_vec(cx: @ext_ctxt, ident: @~str) -> ~[ast::ident] { let intr = cx.parse_sess().interner; return ~[intr.intern(@~"unstable"), intr.intern(@~"extfmt"), intr.intern(@~"rt"), intr.intern(ident)]; } fn make_rt_path_expr(cx: @ext_ctxt, sp: span, nm: @~str) -> @ast::expr { let path = make_path_vec(cx, nm); return mk_path_global(cx, sp, path); } // Produces an AST expression that represents a RT::conv record, // which tells the RT::conv* functions how to perform the conversion fn make_rt_conv_expr(cx: @ext_ctxt, sp: span, cnv: &Conv) -> @ast::expr { fn make_flags(cx: @ext_ctxt, sp: span, flags: ~[Flag]) -> @ast::expr { let mut tmp_expr = make_rt_path_expr(cx, sp, @~"flag_none"); for flags.each |f| { let fstr = match *f { FlagLeftJustify => ~"flag_left_justify", FlagLeftZeroPad => ~"flag_left_zero_pad", FlagSpaceForSign => ~"flag_space_for_sign", FlagSignAlways => ~"flag_sign_always", FlagAlternate => ~"flag_alternate" }; tmp_expr = mk_binary(cx, sp, ast::bitor, tmp_expr, make_rt_path_expr(cx, sp, @fstr)); } return tmp_expr; } fn make_count(cx: @ext_ctxt, sp: span, cnt: Count) -> @ast::expr { match cnt { CountImplied => { return make_rt_path_expr(cx, sp, @~"CountImplied"); } CountIs(c) => { let count_lit = mk_uint(cx, sp, c as uint); let count_is_path = make_path_vec(cx, @~"CountIs"); let count_is_args = ~[count_lit]; return mk_call_global(cx, sp, count_is_path, count_is_args); } _ => cx.span_unimpl(sp, ~"unimplemented fmt! conversion") } } fn make_ty(cx: @ext_ctxt, sp: span, t: Ty) -> @ast::expr { let mut rt_type; match t { TyHex(c) => match c { CaseUpper => rt_type = ~"TyHexUpper", CaseLower => rt_type = ~"TyHexLower" }, TyBits => rt_type = ~"TyBits", TyOctal => rt_type = ~"TyOctal", _ => rt_type = ~"TyDefault" } return make_rt_path_expr(cx, sp, @rt_type); } fn make_conv_struct(cx: @ext_ctxt, sp: span, flags_expr: @ast::expr, width_expr: @ast::expr, precision_expr: @ast::expr, ty_expr: @ast::expr) -> @ast::expr { let intr = cx.parse_sess().interner; mk_global_struct_e( cx, sp, make_path_vec(cx, @~"Conv"), ~[ build::Field { ident: intr.intern(@~"flags"), ex: flags_expr }, build::Field { ident: intr.intern(@~"width"), ex: width_expr }, build::Field { ident: intr.intern(@~"precision"), ex: precision_expr }, build::Field { ident: intr.intern(@~"ty"), ex: ty_expr }, ] ) } let rt_conv_flags = make_flags(cx, sp, cnv.flags); let rt_conv_width = make_count(cx, sp, cnv.width); let rt_conv_precision = make_count(cx, sp, cnv.precision); let rt_conv_ty = make_ty(cx, sp, cnv.ty); make_conv_struct(cx, sp, rt_conv_flags, rt_conv_width, rt_conv_precision, rt_conv_ty) } fn make_conv_call(cx: @ext_ctxt, sp: span, conv_type: ~str, cnv: &Conv, arg: @ast::expr) -> @ast::expr { let fname = ~"conv_" + conv_type; let path = make_path_vec(cx, @fname); let cnv_expr = make_rt_conv_expr(cx, sp, cnv); let args = ~[cnv_expr, arg]; return mk_call_global(cx, arg.span, path, args); } fn make_new_conv(cx: @ext_ctxt, sp: span, cnv: &Conv, arg: @ast::expr) -> @ast::expr { // FIXME: Move validation code into core::extfmt (Issue #2249) fn is_signed_type(cnv: &Conv) -> bool { match cnv.ty { TyInt(s) => match s { Signed => return true, Unsigned => return false }, TyFloat => return true, _ => return false } } let unsupported = ~"conversion not supported in fmt! string"; match cnv.param { option::None => (), _ => cx.span_unimpl(sp, unsupported) } for cnv.flags.each |f| { match *f { FlagLeftJustify => (), FlagSignAlways => { if !is_signed_type(cnv) { cx.span_fatal(sp, ~"+ flag only valid in " + ~"signed fmt! conversion"); } } FlagSpaceForSign => { if !is_signed_type(cnv) { cx.span_fatal(sp, ~"space flag only valid in " + ~"signed fmt! conversions"); } } FlagLeftZeroPad => (), _ => cx.span_unimpl(sp, unsupported) } } match cnv.width { CountImplied => (), CountIs(_) => (), _ => cx.span_unimpl(sp, unsupported) } match cnv.precision { CountImplied => (), CountIs(_) => (), _ => cx.span_unimpl(sp, unsupported) } match cnv.ty { TyStr => return make_conv_call(cx, arg.span, ~"str", cnv, arg), TyInt(sign) => match sign { Signed => return make_conv_call(cx, arg.span, ~"int", cnv, arg), Unsigned => { return make_conv_call(cx, arg.span, ~"uint", cnv, arg) } }, TyBool => return make_conv_call(cx, arg.span, ~"bool", cnv, arg), TyChar => return make_conv_call(cx, arg.span, ~"char", cnv, arg), TyHex(_) => { return make_conv_call(cx, arg.span, ~"uint", cnv, arg); } TyBits => return make_conv_call(cx, arg.span, ~"uint", cnv, arg), TyOctal => return make_conv_call(cx, arg.span, ~"uint", cnv, arg), TyFloat => { return make_conv_call(cx, arg.span, ~"float", cnv, arg); } TyPoly => return make_conv_call(cx, arg.span, ~"poly", cnv, mk_addr_of(cx, sp, arg)) } } fn log_conv(c: &Conv) { debug!("Building conversion:"); match c.param { Some(p) => { debug!("param: %s", p.to_str()); } _ => debug!("param: none") } for c.flags.each |f| { match *f { FlagLeftJustify => debug!("flag: left justify"), FlagLeftZeroPad => debug!("flag: left zero pad"), FlagSpaceForSign => debug!("flag: left space pad"), FlagSignAlways => debug!("flag: sign always"), FlagAlternate => debug!("flag: alternate") } } match c.width { CountIs(i) => debug!("width: count is %s", i.to_str()), CountIsParam(i) => debug!("width: count is param %s", i.to_str()), CountIsNextParam => debug!("width: count is next param"), CountImplied => debug!("width: count is implied") } match c.precision { CountIs(i) => debug!("prec: count is %s", i.to_str()), CountIsParam(i) => debug!("prec: count is param %s", i.to_str()), CountIsNextParam => debug!("prec: count is next param"), CountImplied => debug!("prec: count is implied") } match c.ty { TyBool => debug!("type: bool"), TyStr => debug!("type: str"), TyChar => debug!("type: char"), TyInt(s) => match s { Signed => debug!("type: signed"), Unsigned => debug!("type: unsigned") }, TyBits => debug!("type: bits"), TyHex(cs) => match cs { CaseUpper => debug!("type: uhex"), CaseLower => debug!("type: lhex"), }, TyOctal => debug!("type: octal"), TyFloat => debug!("type: float"), TyPoly => debug!("type: poly") } } /* Translate each piece (portion of the fmt expression) into a ~str expression to be concatenated below */ let fmt_sp = args[0].span; let mut n = 0u; let nargs = args.len(); let pieces = do vec::map_consume(pieces) |pc| { match pc { PieceString(s) => mk_uniq_str(cx, fmt_sp, s), PieceConv(ref conv) => { n += 1u; if n >= nargs { cx.span_fatal(sp, ~"not enough arguments to fmt! " + ~"for the given format string"); } log_conv(conv); make_new_conv(cx, fmt_sp, conv, args[n]) } } }; let expected_nargs = n + 1u; // n conversions + the fmt string if expected_nargs < nargs { cx.span_fatal (sp, fmt!("too many arguments to fmt!. found %u, expected %u", nargs, expected_nargs)); } /* Concatenate all of the strings together with str::push_str. This involves storing the first piece into a local variable, and then pushing each other piece onto the local. The local is contained in its own block to not conflict with other names as much as possible */ let ident = cx.parse_sess().interner.intern(@~"__fmtbuf"); let buf = || mk_path(cx, fmt_sp, ~[ident]); let str_ident = cx.parse_sess().interner.intern(@~"str"); let push_ident = cx.parse_sess().interner.intern(@~"push_str"); let mut first = true; let stms = do vec::map_consume(pieces) |pc| { if first { first = false; mk_local(cx, fmt_sp, true, ident, pc) } else { let call = mk_call_global(cx, fmt_sp, ~[str_ident, push_ident], ~[mk_mut_addr_of(cx, fmt_sp, buf()), pc]); mk_stmt(cx, fmt_sp, call) } }; return mk_block(cx, fmt_sp, ~[], stms, Some(buf())); } // // Local Variables: // mode: rust // fill-column: 78; // indent-tabs-mode: nil // c-basic-offset: 4 // buffer-file-coding-system: utf-8-unix // End: //