| |
| |
| /* |
| * 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 extfmt::ct::*; |
| use base::*; |
| use codemap::span; |
| use ext::build::*; |
| export expand_syntax_ext; |
| |
| fn expand_syntax_ext(cx: ext_ctxt, sp: span, arg: ast::mac_arg, |
| _body: ast::mac_body) -> @ast::expr { |
| let args = get_mac_args_no_max(cx, sp, arg, 1u, ~"fmt"); |
| 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:"); |
| log(debug, fmt); |
| fn parse_fmt_err_(cx: ext_ctxt, sp: span, msg: &str) -> ! { |
| cx.span_fatal(sp, msg); |
| } |
| let parse_fmt_err = fn@(s: &str) -> ! { |
| parse_fmt_err_(cx, fmtspan, s) |
| }; |
| let pieces = parse_fmt_string(fmt, parse_fmt_err); |
| return 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(@~"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(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_int(cx, sp, c); |
| let count_is_path = make_path_vec(cx, @~"CountIs"); |
| let count_is_args = ~[count_lit]; |
| return mk_call(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_rec(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; |
| return mk_rec_e(cx, sp, |
| ~[{ident: intr.intern(@~"flags"), ex: flags_expr}, |
| {ident: intr.intern(@~"width"), ex: width_expr}, |
| {ident: intr.intern(@~"precision"), |
| ex: precision_expr}, |
| {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); |
| return make_conv_rec(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(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) { |
| match c.param { |
| Some(p) => { log(debug, ~"param: " + int::to_str(p, 10u)); } |
| _ => 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) => log( |
| debug, ~"width: count is " + int::to_str(i, 10u)), |
| CountIsParam(i) => log( |
| debug, ~"width: count is param " + int::to_str(i, 10u)), |
| CountIsNextParam => debug!("width: count is next param"), |
| CountImplied => debug!("width: count is implied") |
| } |
| match c.precision { |
| CountIs(i) => log( |
| debug, ~"prec: count is " + int::to_str(i, 10u)), |
| CountIsParam(i) => log( |
| debug, ~"prec: count is param " + int::to_str(i, 10u)), |
| 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") |
| } |
| } |
| let fmt_sp = args[0].span; |
| let mut n = 0u; |
| let mut piece_exprs = ~[]; |
| let nargs = args.len(); |
| for pieces.each |pc| { |
| match *pc { |
| PieceString(s) => { |
| piece_exprs.push(mk_uniq_str(cx, fmt_sp, s)) |
| } |
| PieceConv(conv) => { |
| n += 1u; |
| if n >= nargs { |
| cx.span_fatal(sp, |
| ~"not enough arguments to #fmt " + |
| ~"for the given format string"); |
| } |
| debug!("Building conversion:"); |
| log_conv(conv); |
| let arg_expr = args[n]; |
| let c_expr = make_new_conv(cx, fmt_sp, conv, arg_expr); |
| piece_exprs.push(c_expr); |
| } |
| } |
| } |
| 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)); |
| } |
| |
| let arg_vec = mk_fixed_vec_e(cx, fmt_sp, piece_exprs); |
| return mk_call(cx, fmt_sp, |
| ~[cx.parse_sess().interner.intern(@~"str"), |
| cx.parse_sess().interner.intern(@~"concat")], |
| ~[arg_vec]); |
| } |
| // |
| // Local Variables: |
| // mode: rust |
| // fill-column: 78; |
| // indent-tabs-mode: nil |
| // c-basic-offset: 4 |
| // buffer-file-coding-system: utf-8-unix |
| // End: |
| // |