about summary refs log tree commit diff
path: root/src/lib
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/lib
parent9e2c5f77a413bf99fff1d8b4f0629127a5f9bbd3 (diff)
downloadrust-4c936d7992123e029dedea0380f429b6844a9ab8.tar.gz
rust-4c936d7992123e029dedea0380f429b6844a9ab8.zip
Add #ifmt extension, like #fmt but for istrs. Issue #855
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/extifmt.rs429
-rw-r--r--src/lib/std.rc1
2 files changed, 430 insertions, 0 deletions
diff --git a/src/lib/extifmt.rs b/src/lib/extifmt.rs
new file mode 100644
index 00000000000..ea602891868
--- /dev/null
+++ b/src/lib/extifmt.rs
@@ -0,0 +1,429 @@
+
+
+/* The 'fmt' extension is modeled on the posix printf system.
+ *
+ * A posix conversion ostensibly looks like this:
+ *
+ * %[parameter][flags][width][.precision][length]type
+ *
+ * Given the different numeric type bestiary we have, we omit the 'length'
+ * parameter and support slightly different conversions for 'type':
+ *
+ * %[parameter][flags][width][.precision]type
+ *
+ * we also only support translating-to-rust a tiny subset of the possible
+ * combinations at the moment.
+ */
+import option::none;
+import option::some;
+
+
+/*
+ * We have a 'ct' (compile-time) module that parses format strings into a
+ * sequence of conversions. From those conversions AST fragments are built
+ * that call into properly-typed functions in the 'rt' (run-time) module.
+ * Each of those run-time conversion functions accepts another conversion
+ * description that specifies how to format its output.
+ *
+ * The building of the AST is currently done in a module inside the compiler,
+ * but should migrate over here as the plugin interface is defined.
+ */
+
+// Functions used by the fmt extension at compile time
+mod ct {
+    tag signedness { signed; unsigned; }
+    tag caseness { case_upper; case_lower; }
+    tag ty {
+        ty_bool;
+        ty_str;
+        ty_char;
+        ty_int(signedness);
+        ty_bits;
+        ty_hex(caseness);
+        ty_octal;
+        // FIXME: More types
+    }
+    tag flag {
+        flag_left_justify;
+        flag_left_zero_pad;
+        flag_space_for_sign;
+        flag_sign_always;
+        flag_alternate;
+    }
+    tag count {
+        count_is(int);
+        count_is_param(int);
+        count_is_next_param;
+        count_implied;
+    }
+
+    // A formatted conversion from an expression to a string
+    type conv =
+        {param: option::t<int>,
+         flags: [flag],
+         width: count,
+         precision: count,
+         ty: ty};
+
+
+    // A fragment of the output sequence
+    tag piece { piece_string(istr); piece_conv(conv); }
+    type error_fn = fn(&istr) -> ! ;
+
+    fn parse_fmt_string(s: &istr, error: error_fn) -> [piece] {
+        let pieces: [piece] = [];
+        let lim = istr::byte_len(s);
+        let buf = ~"";
+        fn flush_buf(buf: &istr, pieces: &mutable [piece]) -> istr {
+            if istr::byte_len(buf) > 0u {
+                let piece = piece_string(buf);
+                pieces += [piece];
+            }
+            ret ~"";
+        }
+        let i = 0u;
+        while i < lim {
+            let curr = istr::substr(s, i, 1u);
+            if istr::eq(curr, ~"%") {
+                i += 1u;
+                if i >= lim {
+                    error(~"unterminated conversion at end of string");
+                }
+                let curr2 = istr::substr(s, i, 1u);
+                if istr::eq(curr2, ~"%") {
+                    i += 1u;
+                } else {
+                    buf = flush_buf(buf, pieces);
+                    let rs = parse_conversion(s, i, lim, error);
+                    pieces += [rs.piece];
+                    i = rs.next;
+                }
+            } else { buf += curr; i += 1u; }
+        }
+        buf = flush_buf(buf, pieces);
+        ret pieces;
+    }
+    fn peek_num(s: &istr, i: uint, lim: uint) ->
+       option::t<{num: uint, next: uint}> {
+        if i >= lim { ret none; }
+        let c = s[i];
+        if !('0' as u8 <= c && c <= '9' as u8) { ret option::none; }
+        let n = c - ('0' as u8) as uint;
+        ret alt peek_num(s, i + 1u, lim) {
+              none. { some({num: n, next: i + 1u}) }
+              some(next) {
+                let m = next.num;
+                let j = next.next;
+                some({num: n * 10u + m, next: j})
+              }
+            };
+    }
+    fn parse_conversion(s: &istr, i: uint, lim: uint, error: error_fn) ->
+       {piece: piece, next: uint} {
+        let parm = parse_parameter(s, i, lim);
+        let flags = parse_flags(s, parm.next, lim);
+        let width = parse_count(s, flags.next, lim);
+        let prec = parse_precision(s, width.next, lim);
+        let ty = parse_type(s, prec.next, lim, error);
+        ret {piece:
+                 piece_conv({param: parm.param,
+                             flags: flags.flags,
+                             width: width.count,
+                             precision: prec.count,
+                             ty: ty.ty}),
+             next: ty.next};
+    }
+    fn parse_parameter(s: &istr, i: uint, lim: uint) ->
+       {param: option::t<int>, next: uint} {
+        if i >= lim { ret {param: none, next: i}; }
+        let num = peek_num(s, i, lim);
+        ret alt num {
+              none. { {param: none, next: i} }
+              some(t) {
+                let n = t.num;
+                let j = t.next;
+                if j < lim && s[j] == '$' as u8 {
+                    {param: some(n as int), next: j + 1u}
+                } else { {param: none, next: i} }
+              }
+            };
+    }
+    fn parse_flags(s: &istr, i: uint, lim: uint) ->
+       {flags: [flag], next: uint} {
+        let noflags: [flag] = [];
+        if i >= lim { ret {flags: noflags, next: i}; }
+
+        // FIXME: This recursion generates illegal instructions if the return
+        // value isn't boxed. Only started happening after the ivec conversion
+        fn more_(f: flag, s: &istr, i: uint, lim: uint) ->
+           @{flags: [flag], next: uint} {
+            let next = parse_flags(s, i + 1u, lim);
+            let rest = next.flags;
+            let j = next.next;
+            let curr: [flag] = [f];
+            ret @{flags: curr + rest, next: j};
+        }
+        let more = bind more_(_, s, i, lim);
+        let f = s[i];
+        ret if f == '-' as u8 {
+                *more(flag_left_justify)
+            } else if f == '0' as u8 {
+                *more(flag_left_zero_pad)
+            } else if f == ' ' as u8 {
+                *more(flag_space_for_sign)
+            } else if f == '+' as u8 {
+                *more(flag_sign_always)
+            } else if f == '#' as u8 {
+                *more(flag_alternate)
+            } else { {flags: noflags, next: i} };
+    }
+    fn parse_count(s: &istr, i: uint,
+                   lim: uint) -> {count: count, next: uint} {
+        ret if i >= lim {
+                {count: count_implied, next: i}
+            } else if s[i] == '*' as u8 {
+                let param = parse_parameter(s, i + 1u, lim);
+                let j = param.next;
+                alt param.param {
+                  none. { {count: count_is_next_param, next: j} }
+                  some(n) { {count: count_is_param(n), next: j} }
+                }
+            } else {
+                let num = peek_num(s, i, lim);
+                alt num {
+                  none. { {count: count_implied, next: i} }
+                  some(num) {
+                    {count: count_is(num.num as int), next: num.next}
+                  }
+                }
+            };
+    }
+    fn parse_precision(s: &istr, i: uint, lim: uint) ->
+       {count: count, next: uint} {
+        ret if i >= lim {
+                {count: count_implied, next: i}
+            } else if s[i] == '.' as u8 {
+                let count = parse_count(s, i + 1u, lim);
+
+
+                // If there were no digits specified, i.e. the precision
+                // was ".", then the precision is 0
+                alt count.count {
+                  count_implied. { {count: count_is(0), next: count.next} }
+                  _ { count }
+                }
+            } else { {count: count_implied, next: i} };
+    }
+    fn parse_type(s: &istr, i: uint, lim: uint, error: error_fn) ->
+       {ty: ty, next: uint} {
+        if i >= lim { error(~"missing type in conversion"); }
+        let tstr = istr::substr(s, i, 1u);
+        // TODO: Do we really want two signed types here?
+        // How important is it to be printf compatible?
+        let t =
+            if istr::eq(tstr, ~"b") {
+                ty_bool
+            } else if istr::eq(tstr, ~"s") {
+                ty_str
+            } else if istr::eq(tstr, ~"c") {
+                ty_char
+            } else if istr::eq(tstr, ~"d") || istr::eq(tstr, ~"i") {
+                ty_int(signed)
+            } else if istr::eq(tstr, ~"u") {
+                ty_int(unsigned)
+            } else if istr::eq(tstr, ~"x") {
+                ty_hex(case_lower)
+            } else if istr::eq(tstr, ~"X") {
+                ty_hex(case_upper)
+            } else if istr::eq(tstr, ~"t") {
+                ty_bits
+            } else if istr::eq(tstr, ~"o") {
+                ty_octal
+            } else { error(~"unknown type in conversion: " + tstr) };
+        ret {ty: t, next: i + 1u};
+    }
+}
+
+
+// Functions used by the fmt extension at runtime. For now there are a lot of
+// decisions made a runtime. If it proves worthwhile then some of these
+// conditions can be evaluated at compile-time. For now though it's cleaner to
+// implement it this way, I think.
+mod rt {
+    tag flag {
+        flag_left_justify;
+        flag_left_zero_pad;
+        flag_space_for_sign;
+        flag_sign_always;
+        flag_alternate;
+
+
+        // FIXME: This is a hack to avoid creating 0-length vec exprs,
+        // which have some difficulty typechecking currently. See
+        // comments in front::extfmt::make_flags
+        flag_none;
+    }
+    tag count { count_is(int); count_implied; }
+    tag ty { ty_default; ty_bits; ty_hex_upper; ty_hex_lower; ty_octal; }
+
+    // FIXME: May not want to use a vector here for flags;
+    // instead just use a bool per flag
+    type conv = {flags: [flag], width: count, precision: count, ty: ty};
+
+    fn conv_int(cv: &conv, i: int) -> istr {
+        let radix = 10u;
+        let prec = get_int_precision(cv);
+        let s = int_to_str_prec(i, radix, prec);
+        if 0 <= i {
+            if have_flag(cv.flags, flag_sign_always) {
+                s = ~"+" + s;
+            } else if have_flag(cv.flags, flag_space_for_sign) {
+                s = ~" " + s;
+            }
+        }
+        ret pad(cv, s, pad_signed);
+    }
+    fn conv_uint(cv: &conv, u: uint) -> istr {
+        let prec = get_int_precision(cv);
+        let rs =
+            alt cv.ty {
+              ty_default. { uint_to_str_prec(u, 10u, prec) }
+              ty_hex_lower. { uint_to_str_prec(u, 16u, prec) }
+              ty_hex_upper. { istr::to_upper(uint_to_str_prec(u, 16u, prec)) }
+              ty_bits. { uint_to_str_prec(u, 2u, prec) }
+              ty_octal. { uint_to_str_prec(u, 8u, prec) }
+            };
+        ret pad(cv, rs, pad_unsigned);
+    }
+    fn conv_bool(cv: &conv, b: bool) -> istr {
+        let s = if b { ~"true" } else { ~"false" };
+        // run the boolean conversion through the string conversion logic,
+        // giving it the same rules for precision, etc.
+
+        ret conv_str(cv, s);
+    }
+    fn conv_char(cv: &conv, c: char) -> istr {
+        ret pad(cv, istr::from_char(c), pad_nozero);
+    }
+    fn conv_str(cv: &conv, s: &istr) -> istr {
+        // For strings, precision is the maximum characters
+        // displayed
+
+        // FIXME: substr works on bytes, not chars!
+        let unpadded =
+            alt cv.precision {
+              count_implied. { s }
+              count_is(max) {
+                if max as uint < istr::char_len(s) {
+                    istr::substr(s, 0u, max as uint)
+                } else { s }
+              }
+            };
+        ret pad(cv, unpadded, pad_nozero);
+    }
+
+    // Convert an int to string with minimum number of digits. If precision is
+    // 0 and num is 0 then the result is the empty string.
+    fn int_to_str_prec(num: int, radix: uint, prec: uint) -> istr {
+        ret if num < 0 {
+                ~"-" + uint_to_str_prec(-num as uint, radix, prec)
+            } else { uint_to_str_prec(num as uint, radix, prec) };
+    }
+
+    // Convert a uint to string with a minimum number of digits.  If precision
+    // is 0 and num is 0 then the result is the empty string. Could move this
+    // to uint: but it doesn't seem all that useful.
+    fn uint_to_str_prec(num: uint, radix: uint, prec: uint) -> istr {
+        ret if prec == 0u && num == 0u {
+                ~""
+            } else {
+                let s = uint::to_str(num, radix);
+                let len = istr::char_len(s);
+                if len < prec {
+                    let diff = prec - len;
+                    let pad = str_init_elt('0', diff);
+                    pad + s
+                } else { s }
+            };
+    }
+    fn get_int_precision(cv: &conv) -> uint {
+        ret alt cv.precision {
+              count_is(c) { c as uint }
+              count_implied. { 1u }
+            };
+    }
+
+    // FIXME: This might be useful in str: but needs to be utf8 safe first
+    fn str_init_elt(c: char, n_elts: uint) -> istr {
+        let svec = vec::init_elt::<u8>(c as u8, n_elts);
+
+        ret istr::unsafe_from_bytes(svec);
+    }
+    tag pad_mode { pad_signed; pad_unsigned; pad_nozero; }
+    fn pad(cv: &conv, s: &istr, mode: pad_mode) -> istr {
+        let uwidth;
+        alt cv.width {
+          count_implied. { ret s; }
+          count_is(width) {
+            // FIXME: Maybe width should be uint
+
+            uwidth = width as uint;
+          }
+        }
+        let strlen = istr::char_len(s);
+        if uwidth <= strlen { ret s; }
+        let padchar = ' ';
+        let diff = uwidth - strlen;
+        if have_flag(cv.flags, flag_left_justify) {
+            let padstr = str_init_elt(padchar, diff);
+            ret s + padstr;
+        }
+        let might_zero_pad = false;
+        let signed = false;
+        alt mode {
+          pad_nozero. {
+            // fallthrough
+
+          }
+          pad_signed. { might_zero_pad = true; signed = true; }
+          pad_unsigned. { might_zero_pad = true; }
+        }
+        fn have_precision(cv: &conv) -> bool {
+            ret alt cv.precision { count_implied. { false } _ { true } };
+        }
+        let zero_padding = false;
+        if might_zero_pad && have_flag(cv.flags, flag_left_zero_pad) &&
+               !have_precision(cv) {
+            padchar = '0';
+            zero_padding = true;
+        }
+        let padstr = str_init_elt(padchar, diff);
+        // This is completely heinous. If we have a signed value then
+        // potentially rip apart the intermediate result and insert some
+        // zeros. It may make sense to convert zero padding to a precision
+        // instead.
+
+        if signed && zero_padding && istr::byte_len(s) > 0u {
+            let head = s[0];
+            if head == '+' as u8 || head == '-' as u8 || head == ' ' as u8 {
+                let headstr = istr::unsafe_from_bytes([head]);
+                let bytelen = istr::byte_len(s);
+                let numpart = istr::substr(s, 1u, bytelen - 1u);
+                ret headstr + padstr + numpart;
+            }
+        }
+        ret padstr + s;
+    }
+    fn have_flag(flags: &[flag], f: flag) -> bool {
+        for candidate: flag in flags { if candidate == f { ret true; } }
+        ret false;
+    }
+}
+// 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:
diff --git a/src/lib/std.rc b/src/lib/std.rc
index ffbf26b5928..d39f5004af1 100644
--- a/src/lib/std.rc
+++ b/src/lib/std.rc
@@ -86,6 +86,7 @@ mod sha1;
 mod ebml;
 mod ufind;
 mod extfmt;
+mod extifmt;
 mod box;
 mod getopts;
 mod time;