about summary refs log tree commit diff
path: root/src/comp/syntax/ext
diff options
context:
space:
mode:
authorBrian Anderson <banderson@mozilla.com>2011-08-27 21:27:39 -0700
committerBrian Anderson <andersrb@gmail.com>2011-08-30 21:08:07 -0700
commit4c936d7992123e029dedea0380f429b6844a9ab8 (patch)
treeeb979b905fee971cf9f0b56d7fd5f2483d1a5938 /src/comp/syntax/ext
parent9e2c5f77a413bf99fff1d8b4f0629127a5f9bbd3 (diff)
Add #ifmt extension, like #fmt but for istrs. Issue #855
Diffstat (limited to 'src/comp/syntax/ext')
-rw-r--r--src/comp/syntax/ext/base.rs1
-rw-r--r--src/comp/syntax/ext/ifmt.rs363
2 files changed, 364 insertions, 0 deletions
diff --git a/src/comp/syntax/ext/base.rs b/src/comp/syntax/ext/base.rs
index 24efb38be86..2dade422c75 100644
--- a/src/comp/syntax/ext/base.rs
+++ b/src/comp/syntax/ext/base.rs
@@ -23,6 +23,7 @@ tag syntax_extension {
 fn syntax_expander_table() -> hashmap<istr, syntax_extension> {
     let syntax_expanders = new_str_hash::<syntax_extension>();
     syntax_expanders.insert(~"fmt", normal(ext::fmt::expand_syntax_ext));
+    syntax_expanders.insert(~"ifmt", normal(ext::ifmt::expand_syntax_ext));
     syntax_expanders.insert(~"env", normal(ext::env::expand_syntax_ext));
     syntax_expanders.insert(~"macro",
                             macro_defining(ext::simplext::add_new_extension));
diff --git a/src/comp/syntax/ext/ifmt.rs b/src/comp/syntax/ext/ifmt.rs
new file mode 100644
index 00000000000..c1bd13ac01e
--- /dev/null
+++ b/src/comp/syntax/ext/ifmt.rs
@@ -0,0 +1,363 @@
+
+
+/*
+ * 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.
+ */
+import std::vec;
+import std::str;
+import std::istr;
+import std::option;
+import std::option::none;
+import std::option::some;
+import std::extifmt::ct::*;
+import base::*;
+import codemap::span;
+export expand_syntax_ext;
+
+fn expand_syntax_ext(cx: &ext_ctxt, sp: span, arg: @ast::expr,
+                     _body: &option::t<istr>) -> @ast::expr {
+    let args: [@ast::expr] =
+        alt arg.node {
+          ast::expr_vec(elts, _) { elts }
+          _ {
+            cx.span_fatal(sp, ~"#fmt requires arguments of the form `[...]`.")
+          }
+        };
+    if vec::len::<@ast::expr>(args) == 0u {
+        cx.span_fatal(sp, ~"#fmt requires a format string");
+    }
+    let fmt =
+        expr_to_str(cx, args[0],
+                    ~"first argument to #fmt must be a "
+                    + ~"string literal.");
+    let fmtspan = args[0].span;
+    log "Format string:";
+    log fmt;
+    fn parse_fmt_err_(cx: &ext_ctxt, sp: span, msg: &istr) -> ! {
+        cx.span_fatal(sp, msg);
+    }
+    let parse_fmt_err = bind parse_fmt_err_(cx, fmtspan, _);
+    let pieces = parse_fmt_string(fmt, parse_fmt_err);
+    ret pieces_to_expr(cx, sp, pieces, args);
+}
+
+// FIXME: A lot of these functions for producing expressions can probably
+// be factored out in common with other code that builds expressions.
+// FIXME: Cleanup the naming of these functions
+fn pieces_to_expr(cx: &ext_ctxt, sp: span, pieces: &[piece],
+                  args: &[@ast::expr]) -> @ast::expr {
+    fn make_new_lit(cx: &ext_ctxt, sp: span, lit: ast::lit_) -> @ast::expr {
+        let sp_lit = @{node: lit, span: sp};
+        ret @{id: cx.next_id(), node: ast::expr_lit(sp_lit), span: sp};
+    }
+    fn make_new_str(cx: &ext_ctxt, sp: span, s: &istr) -> @ast::expr {
+        let lit = ast::lit_str(s, ast::sk_unique);
+        ret make_new_lit(cx, sp, lit);
+    }
+    fn make_new_int(cx: &ext_ctxt, sp: span, i: int) -> @ast::expr {
+        let lit = ast::lit_int(i);
+        ret make_new_lit(cx, sp, lit);
+    }
+    fn make_new_uint(cx: &ext_ctxt, sp: span, u: uint) -> @ast::expr {
+        let lit = ast::lit_uint(u);
+        ret make_new_lit(cx, sp, lit);
+    }
+    fn make_add_expr(cx: &ext_ctxt, sp: span, lhs: @ast::expr,
+                     rhs: @ast::expr) -> @ast::expr {
+        let binexpr = ast::expr_binary(ast::add, lhs, rhs);
+        ret @{id: cx.next_id(), node: binexpr, span: sp};
+    }
+    fn make_path_expr(cx: &ext_ctxt, sp: span, idents: &[ast::ident]) ->
+       @ast::expr {
+        let path = {global: false, idents: idents, types: []};
+        let sp_path = {node: path, span: sp};
+        let pathexpr = ast::expr_path(sp_path);
+        ret @{id: cx.next_id(), node: pathexpr, span: sp};
+    }
+    fn make_vec_expr(cx: &ext_ctxt, sp: span, exprs: &[@ast::expr]) ->
+       @ast::expr {
+        let vecexpr = ast::expr_vec(exprs, ast::imm);
+        ret @{id: cx.next_id(), node: vecexpr, span: sp};
+    }
+    fn make_call(cx: &ext_ctxt, sp: span, fn_path: &[ast::ident],
+                 args: &[@ast::expr]) -> @ast::expr {
+        let pathexpr = make_path_expr(cx, sp, fn_path);
+        let callexpr = ast::expr_call(pathexpr, args);
+        ret @{id: cx.next_id(), node: callexpr, span: sp};
+    }
+    fn make_rec_expr(cx: &ext_ctxt, sp: span,
+                     fields: &[{ident: ast::ident, ex: @ast::expr}]) ->
+       @ast::expr {
+        let astfields: [ast::field] = [];
+        for field: {ident: ast::ident, ex: @ast::expr} in fields {
+            let ident = field.ident;
+            let val = field.ex;
+            let astfield =
+                {node: {mut: ast::imm, ident: ident, expr: val}, span: sp};
+            astfields += [astfield];
+        }
+        let recexpr = ast::expr_rec(astfields, option::none::<@ast::expr>);
+        ret @{id: cx.next_id(), node: recexpr, span: sp};
+    }
+    fn make_path_vec(cx: &ext_ctxt, ident: &ast::ident) -> [ast::ident] {
+        fn compiling_std(cx: &ext_ctxt) -> bool {
+            ret istr::find(cx.crate_file_name(), ~"std.rc") >= 0;
+        }
+        if compiling_std(cx) {
+            ret [~"extifmt", ~"rt", ident];
+        } else { ret [~"std", ~"extifmt", ~"rt", ident]; }
+    }
+    fn make_rt_path_expr(cx: &ext_ctxt, sp: span,
+                         ident: &istr) -> @ast::expr {
+        let path = make_path_vec(cx, ident);
+        ret make_path_expr(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 flagexprs: [@ast::expr] = [];
+            for f: flag in flags {
+                let fstr;
+                alt f {
+                  flag_left_justify. { fstr = ~"flag_left_justify"; }
+                  flag_left_zero_pad. { fstr = ~"flag_left_zero_pad"; }
+                  flag_space_for_sign. { fstr = ~"flag_space_for_sign"; }
+                  flag_sign_always. { fstr = ~"flag_sign_always"; }
+                  flag_alternate. { fstr = ~"flag_alternate"; }
+                }
+                flagexprs += [make_rt_path_expr(cx, sp, fstr)];
+            }
+            // FIXME: 0-length vectors can't have their type inferred
+            // through the rec that these flags are a member of, so
+            // this is a hack placeholder flag
+
+            if vec::len::<@ast::expr>(flagexprs) == 0u {
+                flagexprs += [make_rt_path_expr(cx, sp, ~"flag_none")];
+            }
+            ret make_vec_expr(cx, sp, flagexprs);
+        }
+        fn make_count(cx: &ext_ctxt, sp: span, cnt: &count) -> @ast::expr {
+            alt cnt {
+              count_implied. {
+                ret make_rt_path_expr(cx, sp, ~"count_implied");
+              }
+              count_is(c) {
+                let count_lit = make_new_int(cx, sp, c);
+                let count_is_path = make_path_vec(cx, ~"count_is");
+                let count_is_args = [count_lit];
+                ret make_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 rt_type;
+            alt t {
+              ty_hex(c) {
+                alt c {
+                  case_upper. { rt_type = ~"ty_hex_upper"; }
+                  case_lower. { rt_type = ~"ty_hex_lower"; }
+                }
+              }
+              ty_bits. { rt_type = ~"ty_bits"; }
+              ty_octal. { rt_type = ~"ty_octal"; }
+              _ { rt_type = ~"ty_default"; }
+            }
+            ret 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 {
+            ret make_rec_expr(cx, sp,
+                              [{ident: ~"flags", ex: flags_expr},
+                               {ident: ~"width", ex: width_expr},
+                               {ident: ~"precision", ex: precision_expr},
+                               {ident: ~"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);
+        ret 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: &istr,
+                      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];
+        ret make_call(cx, arg.span, path, args);
+    }
+    fn make_new_conv(cx: &ext_ctxt, sp: span, cnv: conv, arg: @ast::expr) ->
+       @ast::expr {
+        // FIXME: Extract all this validation into extfmt::ct
+
+        fn is_signed_type(cnv: conv) -> bool {
+            alt cnv.ty {
+              ty_int(s) {
+                alt s { signed. { ret true; } unsigned. { ret false; } }
+              }
+              _ { ret false; }
+            }
+        }
+        let unsupported = ~"conversion not supported in #fmt string";
+        alt cnv.param {
+          option::none. { }
+          _ { cx.span_unimpl(sp, unsupported); }
+        }
+        for f: flag in cnv.flags {
+            alt f {
+              flag_left_justify. { }
+              flag_sign_always. {
+                if !is_signed_type(cnv) {
+                    cx.span_fatal(sp,
+                                  ~"+ flag only valid in " +
+                                      ~"signed #fmt conversion");
+                }
+              }
+              flag_space_for_sign. {
+                if !is_signed_type(cnv) {
+                    cx.span_fatal(sp,
+                                  ~"space flag only valid in " +
+                                      ~"signed #fmt conversions");
+                }
+              }
+              flag_left_zero_pad. { }
+              _ { cx.span_unimpl(sp, unsupported); }
+            }
+        }
+        alt cnv.width {
+          count_implied. { }
+          count_is(_) { }
+          _ { cx.span_unimpl(sp, unsupported); }
+        }
+        alt cnv.precision {
+          count_implied. { }
+          count_is(_) { }
+          _ { cx.span_unimpl(sp, unsupported); }
+        }
+        alt cnv.ty {
+          ty_str. { ret make_conv_call(cx, arg.span, ~"str", cnv, arg); }
+          ty_int(sign) {
+            alt sign {
+              signed. { ret make_conv_call(cx, arg.span, ~"int", cnv, arg); }
+              unsigned. {
+                ret make_conv_call(cx, arg.span, ~"uint", cnv, arg);
+              }
+            }
+          }
+          ty_bool. { ret make_conv_call(cx, arg.span, ~"bool", cnv, arg); }
+          ty_char. { ret make_conv_call(cx, arg.span, ~"char", cnv, arg); }
+          ty_hex(_) { ret make_conv_call(cx, arg.span, ~"uint", cnv, arg); }
+          ty_bits. { ret make_conv_call(cx, arg.span, ~"uint", cnv, arg); }
+          ty_octal. { ret make_conv_call(cx, arg.span, ~"uint", cnv, arg); }
+          _ { cx.span_unimpl(sp, unsupported); }
+        }
+    }
+    fn log_conv(c: conv) {
+        alt c.param {
+          some(p) {
+            log "param: "
+                + istr::to_estr(std::int::to_str(p, 10u));
+          }
+          _ { log "param: none"; }
+        }
+        for f: flag in c.flags {
+            alt f {
+              flag_left_justify. { log "flag: left justify"; }
+              flag_left_zero_pad. { log "flag: left zero pad"; }
+              flag_space_for_sign. { log "flag: left space pad"; }
+              flag_sign_always. { log "flag: sign always"; }
+              flag_alternate. { log "flag: alternate"; }
+            }
+        }
+        alt c.width {
+          count_is(i) { log "width: count is "
+              + istr::to_estr(std::int::to_str(i, 10u)); }
+          count_is_param(i) {
+            log "width: count is param "
+                + istr::to_estr(std::int::to_str(i, 10u));
+          }
+          count_is_next_param. { log "width: count is next param"; }
+          count_implied. { log "width: count is implied"; }
+        }
+        alt c.precision {
+          count_is(i) { log "prec: count is "
+              + istr::to_estr(std::int::to_str(i, 10u)); }
+          count_is_param(i) {
+            log "prec: count is param "
+                + istr::to_estr(std::int::to_str(i, 10u));
+          }
+          count_is_next_param. { log "prec: count is next param"; }
+          count_implied. { log "prec: count is implied"; }
+        }
+        alt c.ty {
+          ty_bool. { log "type: bool"; }
+          ty_str. { log "type: str"; }
+          ty_char. { log "type: char"; }
+          ty_int(s) {
+            alt s {
+              signed. { log "type: signed"; }
+              unsigned. { log "type: unsigned"; }
+            }
+          }
+          ty_bits. { log "type: bits"; }
+          ty_hex(cs) {
+            alt cs {
+              case_upper. { log "type: uhex"; }
+              case_lower. { log "type: lhex"; }
+            }
+          }
+          ty_octal. { log "type: octal"; }
+        }
+    }
+    let fmt_sp = args[0].span;
+    let n = 0u;
+    let tmp_expr = make_new_str(cx, sp, ~"");
+    let nargs = vec::len::<@ast::expr>(args);
+    for pc: piece in pieces {
+        alt pc {
+          piece_string(s) {
+            let s_expr = make_new_str(cx, fmt_sp, s);
+            tmp_expr = make_add_expr(cx, fmt_sp, tmp_expr, s_expr);
+          }
+          piece_conv(conv) {
+            n += 1u;
+            if n >= nargs {
+                cx.span_fatal(sp,
+                              ~"not enough arguments to #fmt " +
+                                  ~"for the given format string");
+            }
+            log "Building conversion:";
+            log_conv(conv);
+            let arg_expr = args[n];
+            let c_expr = make_new_conv(cx, fmt_sp, conv, arg_expr);
+            tmp_expr = make_add_expr(cx, fmt_sp, tmp_expr, c_expr);
+          }
+        }
+    }
+    let expected_nargs = n + 1u; // n conversions + the fmt string
+
+    if expected_nargs < nargs {
+        cx.span_fatal(
+            sp, istr::from_estr(
+            #fmt["too many arguments to #fmt. found %u, expected %u",
+                 nargs, expected_nargs]));
+    }
+    ret tmp_expr;
+}
+//
+// Local Variables:
+// mode: rust
+// fill-column: 78;
+// indent-tabs-mode: nil
+// c-basic-offset: 4
+// buffer-file-coding-system: utf-8-unix
+// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
+// End:
+//