rust/src/libsyntax/ext/fmt.rs

328 lines
13 KiB
Rust
Raw Normal View History

// Copyright 2012-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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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 ast;
use codemap::Span;
use ext::base::*;
use ext::base;
use ext::build::AstBuilder;
2013-06-24 19:40:33 -05:00
use std::option;
use std::unstable::extfmt::ct::*;
use parse::token::{str_to_ident};
pub fn expand_syntax_ext(cx: @ExtCtxt, sp: Span, tts: &[ast::token_tree])
2013-01-22 18:45:27 -06:00
-> base::MacResult {
let args = get_exprs_from_tts(cx, sp, tts);
if args.len() == 0 {
cx.span_fatal(sp, "fmt! takes at least 1 argument.");
}
let (fmt, _fmt_str_style) =
expr_to_str(cx, args[0],
"first argument to fmt! must be a string literal.");
let fmtspan = args[0].span;
2013-09-27 23:01:58 -05:00
debug2!("Format string: {}", fmt);
fn parse_fmt_err_(cx: @ExtCtxt, 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);
2013-01-22 18:45:27 -06:00
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: @ExtCtxt, sp: Span,
pieces: ~[Piece], args: ~[@ast::Expr])
-> @ast::Expr {
2013-09-01 19:50:59 -05:00
fn make_path_vec(ident: &str) -> ~[ast::Ident] {
return ~[str_to_ident("std"),
str_to_ident("unstable"),
str_to_ident("extfmt"),
str_to_ident("rt"),
str_to_ident(ident)];
}
fn make_rt_path_expr(cx: @ExtCtxt, sp: Span, nm: &str) -> @ast::Expr {
2013-06-04 17:14:56 -05:00
let path = make_path_vec(nm);
cx.expr_path(cx.path_global(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: @ExtCtxt, sp: Span, cnv: &Conv) -> @ast::Expr {
fn make_flags(cx: @ExtCtxt, sp: Span, flags: &[Flag]) -> @ast::Expr {
let mut tmp_expr = make_rt_path_expr(cx, sp, "flag_none");
for f in flags.iter() {
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 = cx.expr_binary(sp, ast::BiBitOr, tmp_expr,
make_rt_path_expr(cx, sp, fstr));
}
2012-08-01 19:30:05 -05:00
return tmp_expr;
}
fn make_count(cx: @ExtCtxt, sp: Span, cnt: Count) -> @ast::Expr {
2012-08-06 14:34:08 -05:00
match cnt {
2012-09-11 21:37:29 -05:00
CountImplied => {
return make_rt_path_expr(cx, sp, "CountImplied");
}
2012-09-11 21:37:29 -05:00
CountIs(c) => {
let count_lit = cx.expr_uint(sp, c as uint);
2013-06-04 17:14:56 -05:00
let count_is_path = make_path_vec("CountIs");
let count_is_args = ~[count_lit];
return cx.expr_call_global(sp, count_is_path, count_is_args);
}
_ => cx.span_unimpl(sp, "unimplemented fmt! conversion")
}
}
fn make_ty(cx: @ExtCtxt, sp: Span, t: Ty) -> @ast::Expr {
let rt_type = match t {
2012-09-11 21:37:29 -05:00
TyHex(c) => match c {
CaseUpper => "TyHexUpper",
CaseLower => "TyHexLower"
},
TyBits => "TyBits",
TyOctal => "TyOctal",
_ => "TyDefault"
};
return make_rt_path_expr(cx, sp, rt_type);
}
fn make_conv_struct(cx: @ExtCtxt, sp: Span, flags_expr: @ast::Expr,
width_expr: @ast::Expr, precision_expr: @ast::Expr,
ty_expr: @ast::Expr) -> @ast::Expr {
cx.expr_struct(
2013-01-24 12:33:20 -06:00
sp,
2013-06-04 17:14:56 -05:00
cx.path_global(sp, make_path_vec("Conv")),
2013-01-24 12:33:20 -06:00
~[
cx.field_imm(sp, str_to_ident("flags"), flags_expr),
cx.field_imm(sp, str_to_ident("width"), width_expr),
cx.field_imm(sp, str_to_ident("precision"), precision_expr),
cx.field_imm(sp, str_to_ident("ty"), ty_expr)
2013-01-24 12:33:20 -06:00
]
)
}
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);
2013-01-24 12:33:20 -06:00
make_conv_struct(cx, sp, rt_conv_flags, rt_conv_width,
rt_conv_precision, rt_conv_ty)
}
fn make_conv_call(cx: @ExtCtxt, sp: Span, conv_type: &str, cnv: &Conv,
arg: @ast::Expr, buf: @ast::Expr) -> @ast::Expr {
let fname = ~"conv_" + conv_type;
2013-06-04 17:14:56 -05:00
let path = make_path_vec(fname);
let cnv_expr = make_rt_conv_expr(cx, sp, cnv);
let args = ~[cnv_expr, arg, buf];
cx.expr_call_global(arg.span, path, args)
}
fn make_new_conv(cx: @ExtCtxt, sp: Span, cnv: &Conv,
arg: @ast::Expr, buf: @ast::Expr) -> @ast::Expr {
2013-03-19 20:24:01 -05:00
fn is_signed_type(cnv: &Conv) -> bool {
2012-08-06 14:34:08 -05:00
match cnv.ty {
2012-09-11 21:37:29 -05:00
TyInt(s) => match s {
Signed => return true,
Unsigned => return false
},
2012-09-11 21:37:29 -05:00
TyFloat => return true,
2012-08-03 21:59:04 -05:00
_ => return false
}
}
let unsupported = ~"conversion not supported in fmt! string";
2012-08-06 14:34:08 -05:00
match cnv.param {
2012-08-20 14:23:37 -05:00
option::None => (),
2012-08-03 21:59:04 -05:00
_ => cx.span_unimpl(sp, unsupported)
}
for f in cnv.flags.iter() {
match *f {
2012-09-11 21:37:29 -05:00
FlagLeftJustify => (),
FlagSignAlways => {
if !is_signed_type(cnv) {
cx.span_fatal(sp,
"+ flag only valid in \
signed fmt! conversion");
}
}
2012-09-11 21:37:29 -05:00
FlagSpaceForSign => {
if !is_signed_type(cnv) {
cx.span_fatal(sp,
"space flag only valid in \
signed fmt! conversions");
}
}
2012-09-11 21:37:29 -05:00
FlagLeftZeroPad => (),
2012-08-03 21:59:04 -05:00
_ => cx.span_unimpl(sp, unsupported)
}
}
2012-08-06 14:34:08 -05:00
match cnv.width {
2012-09-11 21:37:29 -05:00
CountImplied => (),
CountIs(_) => (),
2012-08-03 21:59:04 -05:00
_ => cx.span_unimpl(sp, unsupported)
}
2012-08-06 14:34:08 -05:00
match cnv.precision {
2012-09-11 21:37:29 -05:00
CountImplied => (),
CountIs(_) => (),
2012-08-03 21:59:04 -05:00
_ => cx.span_unimpl(sp, unsupported)
}
let (name, actual_arg) = match cnv.ty {
TyStr => ("str", arg),
TyInt(Signed) => ("int", arg),
TyBool => ("bool", arg),
TyChar => ("char", arg),
TyBits | TyOctal | TyHex(_) | TyInt(Unsigned) => ("uint", arg),
TyFloat => ("float", arg),
TyPointer => ("pointer", arg),
TyPoly => ("poly", cx.expr_addr_of(sp, arg))
};
return make_conv_call(cx, arg.span, name, cnv, actual_arg,
cx.expr_mut_addr_of(arg.span, buf));
}
2013-03-19 20:24:01 -05:00
fn log_conv(c: &Conv) {
2013-09-27 23:01:58 -05:00
debug2!("Building conversion:");
2012-08-06 14:34:08 -05:00
match c.param {
2013-09-27 23:01:58 -05:00
Some(p) => { debug2!("param: {}", p.to_str()); }
_ => debug2!("param: none")
}
for f in c.flags.iter() {
match *f {
2013-09-27 23:01:58 -05:00
FlagLeftJustify => debug2!("flag: left justify"),
FlagLeftZeroPad => debug2!("flag: left zero pad"),
FlagSpaceForSign => debug2!("flag: left space pad"),
FlagSignAlways => debug2!("flag: sign always"),
FlagAlternate => debug2!("flag: alternate")
}
}
2012-08-06 14:34:08 -05:00
match c.width {
2013-03-08 14:39:42 -06:00
CountIs(i) =>
2013-09-27 23:01:58 -05:00
debug2!("width: count is {}", i.to_str()),
2013-03-08 14:39:42 -06:00
CountIsParam(i) =>
2013-09-27 23:01:58 -05:00
debug2!("width: count is param {}", i.to_str()),
CountIsNextParam => debug2!("width: count is next param"),
CountImplied => debug2!("width: count is implied")
}
2012-08-06 14:34:08 -05:00
match c.precision {
2013-03-08 14:39:42 -06:00
CountIs(i) =>
2013-09-27 23:01:58 -05:00
debug2!("prec: count is {}", i.to_str()),
2013-03-08 14:39:42 -06:00
CountIsParam(i) =>
2013-09-27 23:01:58 -05:00
debug2!("prec: count is param {}", i.to_str()),
CountIsNextParam => debug2!("prec: count is next param"),
CountImplied => debug2!("prec: count is implied")
}
2012-08-06 14:34:08 -05:00
match c.ty {
2013-09-27 23:01:58 -05:00
TyBool => debug2!("type: bool"),
TyStr => debug2!("type: str"),
TyChar => debug2!("type: char"),
2012-09-11 21:37:29 -05:00
TyInt(s) => match s {
2013-09-27 23:01:58 -05:00
Signed => debug2!("type: signed"),
Unsigned => debug2!("type: unsigned")
},
2013-09-27 23:01:58 -05:00
TyBits => debug2!("type: bits"),
2012-09-11 21:37:29 -05:00
TyHex(cs) => match cs {
2013-09-27 23:01:58 -05:00
CaseUpper => debug2!("type: uhex"),
CaseLower => debug2!("type: lhex"),
},
2013-09-27 23:01:58 -05:00
TyOctal => debug2!("type: octal"),
TyFloat => debug2!("type: float"),
TyPointer => debug2!("type: pointer"),
TyPoly => debug2!("type: poly")
}
}
2013-06-19 01:16:44 -05:00
/* Short circuit an easy case up front (won't work otherwise) */
if pieces.len() == 0 {
return cx.expr_str_uniq(args[0].span, @"");
}
let fmt_sp = args[0].span;
let mut n = 0u;
let nargs = args.len();
/* 'ident' is the local buffer building up the result of fmt! */
let ident = str_to_ident("__fmtbuf");
let buf = || cx.expr_ident(fmt_sp, ident);
let core_ident = str_to_ident("std");
let str_ident = str_to_ident("str");
let push_ident = str_to_ident("push_str");
let mut stms = ~[];
/* Translate each piece (portion of the fmt expression) by invoking the
2013-06-24 19:40:33 -05:00
corresponding function in std::unstable::extfmt. Each function takes a
buffer to insert data into along with the data being formatted. */
2013-04-12 00:10:31 -05:00
let npieces = pieces.len();
for (i, pc) in pieces.move_iter().enumerate() {
match pc {
/* Raw strings get appended via str::push_str */
PieceString(s) => {
/* If this is the first portion, then initialize the local
2013-04-12 00:10:31 -05:00
buffer with it directly. If it's actually the only piece,
then there's no need for it to be mutable */
if i == 0 {
stms.push(cx.stmt_let(fmt_sp, npieces > 1,
ident, cx.expr_str_uniq(fmt_sp, s.to_managed())));
} else {
// we call the push_str function because the
// bootstrap doesnt't seem to work if we call the
// method.
let args = ~[cx.expr_mut_addr_of(fmt_sp, buf()),
cx.expr_str(fmt_sp, s.to_managed())];
let call = cx.expr_call_global(fmt_sp,
~[core_ident,
str_ident,
push_ident],
args);
stms.push(cx.stmt_expr(call));
}
}
/* Invoke the correct conv function in extfmt */
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);
/* If the first portion is a conversion, then the local buffer
must be initialized as an empty string */
if i == 0 {
stms.push(cx.stmt_let(fmt_sp, true, ident,
cx.expr_str_uniq(fmt_sp, @"")));
}
stms.push(cx.stmt_expr(make_new_conv(cx, fmt_sp, conv,
args[n], buf())));
}
}
}
let expected_nargs = n + 1u; // n conversions + the fmt string
if expected_nargs < nargs {
2011-09-12 05:39:38 -05:00
cx.span_fatal
2013-09-27 23:01:58 -05:00
(sp, format!("too many arguments to fmt!. found {}, expected {}",
2012-08-22 19:24:52 -05:00
nargs, expected_nargs));
}
cx.expr_block(cx.block(fmt_sp, stms, Some(buf())))
}