1 // Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
12 * The compiler code necessary to support the fmt! extension. Eventually this
13 * should all get sucked into either the standard library extfmt module or the
14 * compiler syntax extension plugin interface.
24 use core::unstable::extfmt::ct::*;
26 pub fn expand_syntax_ext(cx: @ext_ctxt, sp: span, tts: &[ast::token_tree])
28 let args = get_exprs_from_tts(cx, tts);
30 cx.span_fatal(sp, "fmt! takes at least 1 argument.");
33 expr_to_str(cx, args[0],
34 ~"first argument to fmt! must be a string literal.");
35 let fmtspan = args[0].span;
36 debug!("Format string: %s", fmt);
37 fn parse_fmt_err_(cx: @ext_ctxt, sp: span, msg: &str) -> ! {
38 cx.span_fatal(sp, msg);
40 let parse_fmt_err: @fn(&str) -> ! = |s| parse_fmt_err_(cx, fmtspan, s);
41 let pieces = parse_fmt_string(fmt, parse_fmt_err);
42 MRExpr(pieces_to_expr(cx, sp, pieces, args))
45 // FIXME (#2249): A lot of these functions for producing expressions can
46 // probably be factored out in common with other code that builds
47 // expressions. Also: Cleanup the naming of these functions.
48 // Note: Moved many of the common ones to build.rs --kevina
49 fn pieces_to_expr(cx: @ext_ctxt, sp: span,
50 pieces: ~[Piece], args: ~[@ast::expr])
52 fn make_path_vec(cx: @ext_ctxt, ident: @~str) -> ~[ast::ident] {
53 let intr = cx.parse_sess().interner;
54 return ~[intr.intern(@~"unstable"), intr.intern(@~"extfmt"),
55 intr.intern(@~"rt"), intr.intern(ident)];
57 fn make_rt_path_expr(cx: @ext_ctxt, sp: span, nm: @~str) -> @ast::expr {
58 let path = make_path_vec(cx, nm);
59 return mk_path_global(cx, sp, path);
61 // Produces an AST expression that represents a RT::conv record,
62 // which tells the RT::conv* functions how to perform the conversion
64 fn make_rt_conv_expr(cx: @ext_ctxt, sp: span, cnv: &Conv) -> @ast::expr {
65 fn make_flags(cx: @ext_ctxt, sp: span, flags: ~[Flag]) -> @ast::expr {
66 let mut tmp_expr = make_rt_path_expr(cx, sp, @~"flag_none");
69 FlagLeftJustify => ~"flag_left_justify",
70 FlagLeftZeroPad => ~"flag_left_zero_pad",
71 FlagSpaceForSign => ~"flag_space_for_sign",
72 FlagSignAlways => ~"flag_sign_always",
73 FlagAlternate => ~"flag_alternate"
75 tmp_expr = mk_binary(cx, sp, ast::bitor, tmp_expr,
76 make_rt_path_expr(cx, sp, @fstr));
80 fn make_count(cx: @ext_ctxt, sp: span, cnt: Count) -> @ast::expr {
83 return make_rt_path_expr(cx, sp, @~"CountImplied");
86 let count_lit = mk_uint(cx, sp, c as uint);
87 let count_is_path = make_path_vec(cx, @~"CountIs");
88 let count_is_args = ~[count_lit];
89 return mk_call_global(cx, sp, count_is_path, count_is_args);
91 _ => cx.span_unimpl(sp, ~"unimplemented fmt! conversion")
94 fn make_ty(cx: @ext_ctxt, sp: span, t: Ty) -> @ast::expr {
98 CaseUpper => rt_type = ~"TyHexUpper",
99 CaseLower => rt_type = ~"TyHexLower"
101 TyBits => rt_type = ~"TyBits",
102 TyOctal => rt_type = ~"TyOctal",
103 _ => rt_type = ~"TyDefault"
105 return make_rt_path_expr(cx, sp, @rt_type);
107 fn make_conv_struct(cx: @ext_ctxt, sp: span, flags_expr: @ast::expr,
108 width_expr: @ast::expr, precision_expr: @ast::expr,
109 ty_expr: @ast::expr) -> @ast::expr {
110 let intr = cx.parse_sess().interner;
114 make_path_vec(cx, @~"Conv"),
117 ident: intr.intern(@~"flags"), ex: flags_expr
120 ident: intr.intern(@~"width"), ex: width_expr
123 ident: intr.intern(@~"precision"), ex: precision_expr
126 ident: intr.intern(@~"ty"), ex: ty_expr
131 let rt_conv_flags = make_flags(cx, sp, cnv.flags);
132 let rt_conv_width = make_count(cx, sp, cnv.width);
133 let rt_conv_precision = make_count(cx, sp, cnv.precision);
134 let rt_conv_ty = make_ty(cx, sp, cnv.ty);
135 make_conv_struct(cx, sp, rt_conv_flags, rt_conv_width,
136 rt_conv_precision, rt_conv_ty)
138 fn make_conv_call(cx: @ext_ctxt, sp: span, conv_type: &str, cnv: &Conv,
139 arg: @ast::expr, buf: @ast::expr) -> @ast::expr {
140 let fname = ~"conv_" + conv_type;
141 let path = make_path_vec(cx, @fname);
142 let cnv_expr = make_rt_conv_expr(cx, sp, cnv);
143 let args = ~[cnv_expr, arg, buf];
144 return mk_call_global(cx, arg.span, path, args);
147 fn make_new_conv(cx: @ext_ctxt, sp: span, cnv: &Conv,
148 arg: @ast::expr, buf: @ast::expr) -> @ast::expr {
149 fn is_signed_type(cnv: &Conv) -> bool {
151 TyInt(s) => match s {
152 Signed => return true,
153 Unsigned => return false
155 TyFloat => return true,
159 let unsupported = ~"conversion not supported in fmt! string";
162 _ => cx.span_unimpl(sp, unsupported)
164 for cnv.flags.each |f| {
166 FlagLeftJustify => (),
168 if !is_signed_type(cnv) {
170 ~"+ flag only valid in " +
171 ~"signed fmt! conversion");
174 FlagSpaceForSign => {
175 if !is_signed_type(cnv) {
177 ~"space flag only valid in " +
178 ~"signed fmt! conversions");
181 FlagLeftZeroPad => (),
182 _ => cx.span_unimpl(sp, unsupported)
188 _ => cx.span_unimpl(sp, unsupported)
190 match cnv.precision {
193 _ => cx.span_unimpl(sp, unsupported)
195 let (name, actual_arg) = match cnv.ty {
196 TyStr => ("str", arg),
197 TyInt(Signed) => ("int", arg),
198 TyBool => ("bool", arg),
199 TyChar => ("char", arg),
200 TyBits | TyOctal | TyHex(_) | TyInt(Unsigned) => ("uint", arg),
201 TyFloat => ("float", arg),
202 TyPoly => ("poly", mk_addr_of(cx, sp, arg))
204 return make_conv_call(cx, arg.span, name, cnv, actual_arg,
205 mk_mut_addr_of(cx, arg.span, buf));
207 fn log_conv(c: &Conv) {
208 debug!("Building conversion:");
210 Some(p) => { debug!("param: %s", p.to_str()); }
211 _ => debug!("param: none")
213 for c.flags.each |f| {
215 FlagLeftJustify => debug!("flag: left justify"),
216 FlagLeftZeroPad => debug!("flag: left zero pad"),
217 FlagSpaceForSign => debug!("flag: left space pad"),
218 FlagSignAlways => debug!("flag: sign always"),
219 FlagAlternate => debug!("flag: alternate")
224 debug!("width: count is %s", i.to_str()),
226 debug!("width: count is param %s", i.to_str()),
227 CountIsNextParam => debug!("width: count is next param"),
228 CountImplied => debug!("width: count is implied")
232 debug!("prec: count is %s", i.to_str()),
234 debug!("prec: count is param %s", i.to_str()),
235 CountIsNextParam => debug!("prec: count is next param"),
236 CountImplied => debug!("prec: count is implied")
239 TyBool => debug!("type: bool"),
240 TyStr => debug!("type: str"),
241 TyChar => debug!("type: char"),
242 TyInt(s) => match s {
243 Signed => debug!("type: signed"),
244 Unsigned => debug!("type: unsigned")
246 TyBits => debug!("type: bits"),
247 TyHex(cs) => match cs {
248 CaseUpper => debug!("type: uhex"),
249 CaseLower => debug!("type: lhex"),
251 TyOctal => debug!("type: octal"),
252 TyFloat => debug!("type: float"),
253 TyPoly => debug!("type: poly")
257 let fmt_sp = args[0].span;
259 let nargs = args.len();
261 /* 'ident' is the local buffer building up the result of fmt! */
262 let ident = cx.parse_sess().interner.intern(@~"__fmtbuf");
263 let buf = || mk_path(cx, fmt_sp, ~[ident]);
264 let str_ident = cx.parse_sess().interner.intern(@~"str");
265 let push_ident = cx.parse_sess().interner.intern(@~"push_str");
268 /* Translate each piece (portion of the fmt expression) by invoking the
269 corresponding function in core::unstable::extfmt. Each function takes a
270 buffer to insert data into along with the data being formatted. */
271 let npieces = pieces.len();
272 do vec::consume(pieces) |i, pc| {
274 /* Raw strings get appended via str::push_str */
276 let portion = mk_uniq_str(cx, fmt_sp, s);
278 /* If this is the first portion, then initialize the local
279 buffer with it directly. If it's actually the only piece,
280 then there's no need for it to be mutable */
282 stms.push(mk_local(cx, fmt_sp, npieces > 1, ident, portion));
284 let args = ~[mk_mut_addr_of(cx, fmt_sp, buf()), portion];
285 let call = mk_call_global(cx,
287 ~[str_ident, push_ident],
289 stms.push(mk_stmt(cx, fmt_sp, call));
293 /* Invoke the correct conv function in extfmt */
294 PieceConv(ref conv) => {
298 ~"not enough arguments to fmt! " +
299 ~"for the given format string");
303 /* If the first portion is a conversion, then the local buffer
304 must be initialized as an empty string */
306 stms.push(mk_local(cx, fmt_sp, true, ident,
307 mk_uniq_str(cx, fmt_sp, ~"")));
309 stms.push(mk_stmt(cx, fmt_sp,
310 make_new_conv(cx, fmt_sp, conv,
316 let expected_nargs = n + 1u; // n conversions + the fmt string
317 if expected_nargs < nargs {
319 (sp, fmt!("too many arguments to fmt!. found %u, expected %u",
320 nargs, expected_nargs));
323 return mk_block(cx, fmt_sp, ~[], stms, Some(buf()));