about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
authorMarijn Haverbeke <marijnh@gmail.com>2011-03-24 12:11:32 +0100
committerGraydon Hoare <graydon@mozilla.com>2011-03-25 08:22:52 -0700
commita0455144774de6c9dc0ff0e87fe4352f8a70cac3 (patch)
treea487499a7e61e5fbda47d93eba806fb02373d1ed /src/lib
parentd3b49f5aab6a9e9efc2ab1d6713cc0d2bde94f4e (diff)
downloadrust-a0455144774de6c9dc0ff0e87fe4352f8a70cac3.tar.gz
rust-a0455144774de6c9dc0ff0e87fe4352f8a70cac3.zip
Start making the standard-lib utf-8 aware
Finally implements _str.is_utf8, adds from_chars, from_char, to_chars,
char_at, char_len, (push|pop|shift|unshift)_char. Also, proper
character I/O for streams.
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/_str.rs167
-rw-r--r--src/lib/ebml.rs8
-rw-r--r--src/lib/fs.rs2
-rw-r--r--src/lib/io.rs91
-rw-r--r--src/lib/std.rc3
5 files changed, 229 insertions, 42 deletions
diff --git a/src/lib/_str.rs b/src/lib/_str.rs
index 87eef514127..31d0790d22d 100644
--- a/src/lib/_str.rs
+++ b/src/lib/_str.rs
@@ -10,6 +10,7 @@ native "rust" mod rustrt {
     fn str_from_vec(vec[mutable? u8] b) -> str;
     fn str_from_cstr(sbuf cstr) -> str;
     fn str_from_buf(sbuf buf, uint len) -> str;
+    fn str_push_byte(str s, uint byte) -> str;
     fn refcount[T](str s) -> uint;
 }
 
@@ -65,15 +66,42 @@ fn hash(&str s) -> uint {
     ret u;
 }
 
+// UTF-8 tags and ranges
+const u8 tag_cont_u8 = 0x80_u8;
+const uint tag_cont = 0x80_u;
+const uint max_one_b = 0x80_u;
+const uint tag_two_b = 0xc0_u;
+const uint max_two_b = 0x800_u;
+const uint tag_three_b = 0xe0_u;
+const uint max_three_b = 0x10000_u;
+const uint tag_four_b = 0xf0_u;
+const uint max_four_b = 0x200000_u;
+const uint tag_five_b = 0xf8_u;
+const uint max_five_b = 0x4000000_u;
+const uint tag_six_b = 0xfc_u;
+
 fn is_utf8(vec[u8] v) -> bool {
-    fail; // FIXME
+    auto i = 0u;
+    auto total = _vec.len[u8](v);
+    while (i < total) {
+        auto chsize = utf8_char_width(v.(i));
+        if (chsize == 0u) {ret false;}
+        if (i + chsize > total) {ret false;}
+        i += 1u;
+        while (chsize > 1u) {
+            if (v.(i) & 0xc0_u8 != tag_cont_u8) {ret false;}
+            i += 1u;
+            chsize -= 1u;
+        }
+    }
+    ret true;
 }
 
 fn is_ascii(str s) -> bool {
     let uint i = byte_len(s);
     while (i > 0u) {
         i -= 1u;
-        if ((s.(i) & 0x80u8) != 0u8) {
+        if ((s.(i) & 0x80_u8) != 0u8) {
             ret false;
         }
     }
@@ -134,6 +162,139 @@ unsafe fn str_from_buf(sbuf buf, uint len) -> str {
     ret rustrt.str_from_buf(buf, len);
 }
 
+fn push_utf8_bytes(&mutable str s, char ch) {
+    auto code = ch as uint;
+    if (code < max_one_b) {
+        s = rustrt.str_push_byte(s, code);
+    } else if (code < max_two_b) {
+        s = rustrt.str_push_byte(s, ((code >> 6u) & 0x1f_u) | tag_two_b);
+        s = rustrt.str_push_byte(s, (code & 0x3f_u) | tag_cont);
+    } else if (code < max_three_b) {
+        s = rustrt.str_push_byte(s, ((code >> 12u) & 0x0f_u) | tag_three_b);
+        s = rustrt.str_push_byte(s, ((code >> 6u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, (code & 0x3f_u) | tag_cont);
+    } else if (code < max_four_b) {
+        s = rustrt.str_push_byte(s, ((code >> 18u) & 0x07_u) | tag_four_b);
+        s = rustrt.str_push_byte(s, ((code >> 12u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, ((code >> 6u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, (code & 0x3f_u) | tag_cont);
+    } else if (code < max_five_b) {
+        s = rustrt.str_push_byte(s, ((code >> 24u) & 0x03_u) | tag_five_b);
+        s = rustrt.str_push_byte(s, ((code >> 18u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, ((code >> 12u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, ((code >> 6u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, (code & 0x3f_u) | tag_cont);
+    } else {
+        s = rustrt.str_push_byte(s, ((code >> 30u) & 0x01_u) | tag_six_b);
+        s = rustrt.str_push_byte(s, ((code >> 24u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, ((code >> 18u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, ((code >> 12u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, ((code >> 6u) & 0x3f_u) | tag_cont);
+        s = rustrt.str_push_byte(s, (code & 0x3f_u) | tag_cont);
+    }
+}
+
+fn from_char(char ch) -> str {
+    auto buf = "";
+    push_utf8_bytes(buf, ch);
+    ret buf;
+}
+
+fn from_chars(vec[char] chs) -> str {
+    auto buf = "";
+    for (char ch in chs) {push_utf8_bytes(buf, ch);}
+    ret buf;
+}
+
+fn utf8_char_width(u8 b) -> uint {
+    let uint byte = b as uint;
+    if (byte < 0x80_u) {ret 1u;}
+    if (byte < 0xc0_u) {ret 0u;} // Not a valid start byte
+    if (byte < 0xe0_u) {ret 2u;}
+    if (byte < 0xf0_u) {ret 3u;}
+    if (byte < 0xf8_u) {ret 4u;}
+    if (byte < 0xfc_u) {ret 5u;}
+    ret 6u;
+}
+
+fn char_range_at(str s, uint i) -> tup(char, uint) {
+    auto b0 = s.(i);
+    auto w = utf8_char_width(b0);
+    check(w != 0u);
+    if (w == 1u) {ret tup(b0 as char, i + 1u);}
+    auto val = 0u;
+    auto end = i + w;
+    i += 1u;
+    while (i < end) {
+        auto byte = s.(i);
+        check(byte & 0xc0_u8 == tag_cont_u8);
+        val <<= 6u;
+        val += (byte & 0x3f_u8) as uint;
+        i += 1u;
+    }
+    // Clunky way to get the right bits from the first byte. Uses two shifts,
+    // the first to clip off the marker bits at the left of the byte, and then
+    // a second (as uint) to get it to the right position.
+    val += ((b0 << ((w + 1u) as u8)) as uint) << ((w - 1u) * 6u - w - 1u);
+    ret tup(val as char, i);
+}
+
+fn char_at(str s, uint i) -> char {
+    ret char_range_at(s, i)._0;
+}
+
+fn char_len(str s) -> uint {
+    auto i = 0u;
+    auto len = 0u;
+    auto total = byte_len(s);
+    while (i < total) {
+        auto chsize = utf8_char_width(s.(i));
+        check(chsize > 0u);
+        len += 1u;
+        i += chsize;
+    }
+    check(i == total);
+    ret len;
+}
+
+fn to_chars(str s) -> vec[char] {
+    let vec[char] buf = vec();
+    auto i = 0u;
+    auto len = byte_len(s);
+    while (i < len) {
+        auto cur = char_range_at(s, i);
+        _vec.push[char](buf, cur._0);
+        i = cur._1;
+    }
+    ret buf;
+}
+
+fn push_char(&mutable str s, char ch) {
+    s += from_char(ch);
+}
+
+fn pop_char(&mutable str s) -> char {
+    auto end = byte_len(s);
+    while (end > 0u && s.(end - 1u) & 0xc0_u8 == tag_cont_u8) {end -= 1u;}
+    check(end > 0u);
+    auto ch = char_at(s, end - 1u);
+    s = substr(s, 0u, end - 1u);
+    ret ch;
+}
+
+fn shift_char(&mutable str s) -> char {
+    auto r = char_range_at(s, 0u);
+    s = substr(s, r._1, byte_len(s) - r._1);
+    ret r._0;
+}
+
+fn unshift_char(&mutable str s, char ch) {
+    // Workaround for rustboot order-of-evaluation issue -- if I put s
+    // directly after the +, the string ends up containing (only) the
+    // character, twice.
+    auto x = s;
+    s = from_char(ch) + x;
+}
 
 fn refcount(str s) -> uint {
     auto r = rustrt.refcount[u8](s);
@@ -256,7 +417,7 @@ fn pop_byte(&mutable str s) -> u8 {
 }
 
 fn push_byte(&mutable str s, u8 b) {
-    s += unsafe_from_byte(b);
+    s = rustrt.str_push_byte(s, b as uint);
 }
 
 fn unshift_byte(&mutable str s, u8 b) {
diff --git a/src/lib/ebml.rs b/src/lib/ebml.rs
index 5eb170224d1..d1697ebaeed 100644
--- a/src/lib/ebml.rs
+++ b/src/lib/ebml.rs
@@ -21,18 +21,18 @@ type reader = rec(
 
 // TODO: eventually use u64 or big here
 impure fn read_vint(&io.reader reader) -> uint {
-    auto a = reader.read_byte();
+    auto a = reader.read_byte() as u8;
     if (a & 0x80u8 != 0u8) { ret (a & 0x7fu8) as uint; }
-    auto b = reader.read_byte();
+    auto b = reader.read_byte() as u8;
     if (a & 0x40u8 != 0u8) {
         ret (((a & 0x3fu8) as uint) << 8u) | (b as uint);
     }
-    auto c = reader.read_byte();
+    auto c = reader.read_byte() as u8;
     if (a & 0x20u8 != 0u8) {
         ret (((a & 0x1fu8) as uint) << 16u) | ((b as uint) << 8u) |
             (c as uint);
     }
-    auto d = reader.read_byte();
+    auto d = reader.read_byte() as u8;
     if (a & 0x10u8 != 0u8) {
         ret (((a & 0x0fu8) as uint) << 24u) | ((b as uint) << 16u) |
             ((c as uint) << 8u) | (d as uint);
diff --git a/src/lib/fs.rs b/src/lib/fs.rs
index 677bbcc4ec8..5bced0ba071 100644
--- a/src/lib/fs.rs
+++ b/src/lib/fs.rs
@@ -3,7 +3,7 @@ native "rust" mod rustrt {
 }
 
 fn path_sep() -> str {
-  ret _str.unsafe_from_bytes(vec(os_fs.path_sep as u8));
+    ret _str.from_char(os_fs.path_sep);
 }
 
 type path = str;
diff --git a/src/lib/io.rs b/src/lib/io.rs
index dea15a27a1d..39399aaa8bc 100644
--- a/src/lib/io.rs
+++ b/src/lib/io.rs
@@ -7,16 +7,16 @@ native "rust" mod rustrt {
 
 // Reading
 
-// TODO This is all buffered. We might need an unbuffered variant as well
+// FIXME This is all buffered. We might need an unbuffered variant as well
 
 tag seek_style {seek_set; seek_end; seek_cur;}
 
 type reader =
     state obj {
-          impure fn read_byte() -> u8;
+          impure fn read_byte() -> int;
+          impure fn unread_byte(int byte);
           impure fn read_bytes(uint len) -> vec[u8];
-          impure fn read_char() -> int;
-          impure fn unread_char(int i);
+          impure fn read_char() -> char;
           impure fn eof() -> bool;
           impure fn read_line() -> str;
           impure fn read_c_str() -> str;
@@ -24,7 +24,7 @@ type reader =
           impure fn read_le_int(uint size) -> int;
 
           impure fn seek(int offset, seek_style whence);
-          impure fn tell() -> uint; // TODO: eventually u64
+          impure fn tell() -> uint; // FIXME: eventually u64
     };
 
 fn convert_whence(seek_style whence) -> int {
@@ -36,8 +36,11 @@ fn convert_whence(seek_style whence) -> int {
 }
 
 state obj FILE_reader(os.libc.FILE f, bool must_close) {
-    impure fn read_byte() -> u8 {
-        ret os.libc.fgetc(f) as u8;
+    impure fn read_byte() -> int {
+        ret os.libc.fgetc(f);
+    }
+    impure fn unread_byte(int byte) {
+        os.libc.ungetc(byte, f);
     }
     impure fn read_bytes(uint len) -> vec[u8] {
         auto buf = _vec.alloc[u8](len);
@@ -45,12 +48,26 @@ state obj FILE_reader(os.libc.FILE f, bool must_close) {
         _vec.len_set[u8](buf, read);
         ret buf;
     }
-    impure fn read_char() -> int {
-        ret os.libc.fgetc(f);
-    }
-    impure fn unread_char(int ch) {
-        os.libc.ungetc(ch, f);
-    }
+    impure fn read_char() -> char {
+        auto c0 = os.libc.fgetc(f);
+        if (c0 == -1) {ret -1 as char;} // FIXME will this stay valid?
+        auto b0 = c0 as u8;
+        auto w = _str.utf8_char_width(b0);
+        check(w > 0u);
+        if (w == 1u) {ret b0 as char;}
+        auto val = 0u;
+        while (w > 1u) {
+            w -= 1u;
+            auto next = os.libc.fgetc(f);
+            check(next > -1);
+            check(next & 0xc0 == 0x80);
+            val <<= 6u;
+            val += (next & 0x3f) as uint;
+        }
+        // See _str.char_at
+        val += ((b0 << ((w + 1u) as u8)) as uint) << ((w - 1u) * 6u - w - 1u);
+        ret val as char;
+    }        
     impure fn eof() -> bool {
       auto ch = os.libc.fgetc(f);
       if (ch == -1) {ret true;}
@@ -58,25 +75,27 @@ state obj FILE_reader(os.libc.FILE f, bool must_close) {
       ret false;
     }
     impure fn read_line() -> str {
-      auto buf = "";
-      while (true) {
-        auto ch = os.libc.fgetc(f);
-        if (ch == -1) { ret buf; }
-        if (ch == 10) { ret buf; }
-        buf += _str.unsafe_from_bytes(vec(ch as u8));
-      }
-      ret buf;
+        let vec[u8] buf = vec();
+        // No break yet in rustc
+        auto go_on = true;
+        while (go_on) {
+            auto ch = os.libc.fgetc(f);
+            if (ch == -1 || ch == 10) {go_on = false;}
+            else {_vec.push[u8](buf, ch as u8);}
+        }
+        ret _str.unsafe_from_bytes(buf);
     }
     impure fn read_c_str() -> str {
-        auto buf = "";
-        while (true) {
+        let vec[u8] buf = vec();
+        auto go_on = true;
+        while (go_on) {
             auto ch = os.libc.fgetc(f);
-            if (ch < 1) { ret buf; }
-            buf += _str.unsafe_from_bytes(vec(ch as u8));
+            if (ch < 1) {go_on = false;}
+            else {_vec.push[u8](buf, ch as u8);}
         }
-        ret buf;
+        ret _str.unsafe_from_bytes(buf);
     }
-    // TODO deal with eof?
+    // FIXME deal with eof?
     impure fn read_le_uint(uint size) -> uint {
         auto val = 0u;
         auto pos = 0u;
@@ -95,7 +114,7 @@ state obj FILE_reader(os.libc.FILE f, bool must_close) {
             pos += 8u;
             size -= 1u;
         }
-        ret val as int; // TODO does that work?
+        ret val as int;
     }
     impure fn seek(int offset, seek_style whence) {
         check(os.libc.fseek(f, offset, convert_whence(whence)) == 0);
@@ -123,8 +142,6 @@ fn file_reader(str path) -> reader {
 
 // Writing
 
-// TODO This is all unbuffered. We might need a buffered variant as well
-
 tag fileflag {
     append;
     create;
@@ -136,7 +153,7 @@ type buf_writer = state obj {
   fn write(vec[u8] v);
 
   fn seek(int offset, seek_style whence);
-  fn tell() -> uint; // TODO: eventually u64
+  fn tell() -> uint; // FIXME: eventually u64
 };
 
 state obj FILE_writer(os.libc.FILE f, bool must_close) {
@@ -224,7 +241,10 @@ fn file_buf_writer(str path, vec[fileflag] flags) -> buf_writer {
 type writer =
     state obj {
           fn get_buf_writer() -> buf_writer;
+          // write_str will continue to do utf-8 output only. an alternative
+          // function will be provided for general encoded string output
           impure fn write_str(str s);
+          impure fn write_char(char ch);
           impure fn write_int(int n);
           impure fn write_uint(uint n);
           impure fn write_bytes(vec[u8] bytes);
@@ -249,6 +269,10 @@ state obj new_writer(buf_writer out) {
     impure fn write_str(str s) {
         out.write(_str.bytes(s));
     }
+    impure fn write_char(char ch) {
+        // FIXME needlessly consy
+        out.write(_str.bytes(_str.from_char(ch)));
+    }
     impure fn write_int(int n) {
         out.write(_str.bytes(_int.to_str(n, 10u)));
     }
@@ -275,7 +299,7 @@ fn file_writer(str path, vec[fileflag] flags) -> writer {
     ret new_writer(file_buf_writer(path, flags));
 }
 
-// TODO: fileflags
+// FIXME: fileflags
 fn buffered_file_buf_writer(str path) -> buf_writer {
     auto f = os.libc.fopen(_str.buf(path), _str.buf("w"));
     if (f as uint == 0u) {
@@ -300,7 +324,7 @@ type byte_buf = @rec(mutable vec[mutable u8] buf, mutable uint pos);
 
 state obj byte_buf_writer(byte_buf buf) {
     fn write(vec[u8] v) {
-        // TODO: optimize
+        // FIXME: optimize
         auto vlen = _vec.len[u8](v);
         auto vpos = 0u;
         while (vpos < vlen) {
@@ -336,7 +360,6 @@ state obj byte_buf_writer(byte_buf buf) {
     fn tell() -> uint { ret buf.pos; }
 }
 
-// TODO awkward! it's not possible to implement a writer with an extra method
 fn string_writer() -> str_writer {
     // FIXME: yikes, this is bad. Needs fixing of mutable syntax.
     let vec[mutable u8] b = vec(mutable 0u8);
diff --git a/src/lib/std.rc b/src/lib/std.rc
index e434769a8dc..e691fb9d72c 100644
--- a/src/lib/std.rc
+++ b/src/lib/std.rc
@@ -41,6 +41,9 @@ auth _task = unsafe;
 auth _str.unshift_byte = impure;
 auth _str.shift_byte = impure;
 auth _str.pop_byte = impure;
+auth _str.unshift_char = impure;
+auth _str.shift_char = impure;
+auth _str.pop_char = impure;
 auth _vec.shift = impure;
 auth _vec.unshift = impure;
 auth _vec.pop = impure;