X-Git-Url: http://nitlanguage.org diff --git a/lib/standard/string.nit b/lib/standard/string.nit index 3d26803..e78f655 100644 --- a/lib/standard/string.nit +++ b/lib/standard/string.nit @@ -350,12 +350,12 @@ abstract class Text # # assert " \n\thello \n\t".l_trim == "hello \n\t" # - # A whitespace is defined as any character which ascii value is less than or equal to 32 + # `Char::is_whitespace` determines what is a whitespace. fun l_trim: SELFTYPE do var iter = self.chars.iterator while iter.is_ok do - if iter.item.ascii > 32 then break + if not iter.item.is_whitespace then break iter.next end if iter.index == length then return self.empty @@ -366,12 +366,12 @@ abstract class Text # # assert " \n\thello \n\t".r_trim == " \n\thello" # - # A whitespace is defined as any character which ascii value is less than or equal to 32 + # `Char::is_whitespace` determines what is a whitespace. fun r_trim: SELFTYPE do var iter = self.chars.reverse_iterator while iter.is_ok do - if iter.item.ascii > 32 then break + if not iter.item.is_whitespace then break iter.next end if iter.index < 0 then return self.empty @@ -379,23 +379,61 @@ abstract class Text end # Trims trailing and preceding white spaces - # A whitespace is defined as any character which ascii value is less than or equal to 32 # # assert " Hello World ! ".trim == "Hello World !" # assert "\na\nb\tc\t".trim == "a\nb\tc" + # + # `Char::is_whitespace` determines what is a whitespace. fun trim: SELFTYPE do return (self.l_trim).r_trim - # Returns `self` removed from its last `\n` (if any). + # Is the string non-empty but only made of whitespaces? + # + # assert " \n\t ".is_whitespace == true + # assert " hello ".is_whitespace == false + # assert "".is_whitespace == false + # + # `Char::is_whitespace` determines what is a whitespace. + fun is_whitespace: Bool + do + if is_empty then return false + for c in self.chars do + if not c.is_whitespace then return false + end + return true + end + + # Returns `self` removed from its last line terminator (if any). # # assert "Hello\n".chomp == "Hello" # assert "Hello".chomp == "Hello" - # assert "\n\n\n".chomp == "\n\n" # - # This method is mainly used to remove the LINE_FEED character from lines of text. + # assert "\n".chomp == "" + # assert "".chomp == "" + # + # Line terminators are `"\n"`, `"\r\n"` and `"\r"`. + # A single line terminator, the last one, is removed. + # + # assert "\r\n".chomp == "" + # assert "\r\n\n".chomp == "\r\n" + # assert "\r\n\r\n".chomp == "\r\n" + # assert "\r\n\r".chomp == "\r\n" + # + # Note: unlike with most IO methods like `Reader::read_line`, + # a single `\r` is considered here to be a line terminator and will be removed. fun chomp: SELFTYPE do - if self.chars.last != '\n' then return self - return substring(0, length-1) + var len = length + if len == 0 then return self + var l = self.chars.last + if l == '\r' then + return substring(0, len-1) + else if l != '\n' then + return self + else if len > 1 and self.chars[len-2] == '\r' then + return substring(0, len-2) + else + return substring(0, len-1) + end end # Justify a self in a space of `length` @@ -418,22 +456,48 @@ abstract class Text # REQUIRE: `left >= 0.0 and left <= 1.0` # ENSURE: `self.length <= length implies result.length == length` # ENSURE: `self.length >= length implies result == self` - fun justify(length: Int, left: Float): SELFTYPE + fun justify(length: Int, left: Float): String do var diff = length - self.length - if diff <= 0 then return self + if diff <= 0 then return to_s assert left >= 0.0 and left <= 1.0 var before = (diff.to_f * left).to_i return " " * before + self + " " * (diff-before) end - # Mangle a string to be a unique string only made of alphanumeric characters + # Mangle a string to be a unique string only made of alphanumeric characters and underscores. + # + # This method is injective (two different inputs never produce the same + # output) and the returned string always respect the following rules: + # + # * Contains only US-ASCII letters, digits and underscores. + # * Never starts with a digit. + # * Never ends with an underscore. + # * Never contains two contiguous underscores. + # + # assert "42_is/The answer!".to_cmangle == "_52d2_is_47dThe_32danswer_33d" + # assert "__".to_cmangle == "_95d_95d" + # assert "__d".to_cmangle == "_95d_d" + # assert "_d_".to_cmangle == "_d_95d" + # assert "_42".to_cmangle == "_95d42" + # assert "foo".to_cmangle == "foo" + # assert "".to_cmangle == "" fun to_cmangle: String do + if is_empty then return "" var res = new FlatBuffer var underscore = false - for i in [0..length[ do - var c = chars[i] + var start = 0 + var c = chars[0] + + if c >= '0' and c <= '9' then + res.add('_') + res.append(c.ascii.to_s) + res.add('d') + start = 1 + end + for i in [start..length[ do + c = chars[i] if (c >= 'a' and c <= 'z') or (c >='A' and c <= 'Z') then res.add(c) underscore = false @@ -456,6 +520,10 @@ abstract class Text underscore = false end end + if underscore then + res.append('_'.ascii.to_s) + res.add('d') + end return res.to_s end @@ -660,7 +728,7 @@ abstract class Text # assert "a&b-<>\"x\"/'".html_escape == "a&b-<>"x"/'" # # SEE: - fun html_escape: SELFTYPE + fun html_escape: String do var buf = new FlatBuffer @@ -826,7 +894,7 @@ end abstract class String super Text - redef type SELFTYPE: String + redef type SELFTYPE: String is fixed redef fun to_s do return self @@ -1001,7 +1069,7 @@ class FlatString # Indes in _items of the last item of the string private var index_to: Int is noinit - redef var chars: SequenceRead[Char] = new FlatStringCharView(self) + redef var chars: SequenceRead[Char] = new FlatStringCharView(self) is lazy redef fun [](index) do @@ -1041,15 +1109,19 @@ class FlatString from = 0 end - var realFrom = index_from + from + var new_from = index_from + from - if (realFrom + count) > index_to then return new FlatString.with_infos(items, index_to - realFrom + 1, realFrom, index_to) + if (new_from + count) > index_to then + var new_len = index_to - new_from + 1 + if new_len <= 0 then return empty + return new FlatString.with_infos(items, new_len, new_from, index_to) + end - if count == 0 then return empty + if count <= 0 then return empty - var to = realFrom + count - 1 + var to = new_from + count - 1 - return new FlatString.with_infos(items, to - realFrom + 1, realFrom, to) + return new FlatString.with_infos(items, to - new_from + 1, new_from, to) end redef fun empty do return "".as(FlatString) @@ -1108,10 +1180,14 @@ class FlatString # String Specific Methods # ################################################## - private init with_infos(items: NativeString, len: Int, from: Int, to: Int) + # Low-level creation of a new string with given data. + # + # `items` will be used as is, without copy, to retrieve the characters of the string. + # Aliasing issues is the responsibility of the caller. + private init with_infos(items: NativeString, length: Int, from: Int, to: Int) do self.items = items - length = len + self.length = length index_from = from index_to = to end @@ -1344,7 +1420,7 @@ end abstract class Buffer super Text - redef type SELFTYPE: Buffer + redef type SELFTYPE: Buffer is fixed # Specific implementations MUST set this to `true` in order to invalidate caches protected var is_dirty = true @@ -1467,9 +1543,7 @@ class FlatBuffer super FlatText super Buffer - redef type SELFTYPE: FlatBuffer - - redef var chars: Sequence[Char] = new FlatBufferCharView(self) + redef var chars: Sequence[Char] = new FlatBufferCharView(self) is lazy private var capacity: Int = 0 @@ -1557,6 +1631,20 @@ class FlatBuffer # Create a new empty string. init do end + # Low-level creation a new buffer with given data. + # + # `items` will be used as is, without copy, to store the characters of the buffer. + # Aliasing issues is the responsibility of the caller. + # + # If `items` is shared, `written` should be set to true after the creation + # so that a modification will do a copy-on-write. + private init with_infos(items: NativeString, capacity, length: Int) + do + self.items = items + self.length = length + self.capacity = capacity + end + # Create a new string copied from `s`. init from(s: Text) do @@ -1581,7 +1669,6 @@ class FlatBuffer init with_capacity(cap: Int) do assert cap >= 0 - # _items = new NativeString.calloc(cap) items = new NativeString(cap+1) capacity = cap length = 0 @@ -1625,11 +1712,10 @@ class FlatBuffer if from < 0 then from = 0 if count > length then count = length if from < count then - var r = new FlatBuffer.with_capacity(count - from) - while from < count do - r.chars.push(items[from]) - from += 1 - end + var len = count - from + var r_items = new NativeString(len) + items.copy_to(r_items, len, from, 0) + var r = new FlatBuffer.with_infos(r_items, len, len) return r else return new FlatBuffer @@ -1856,15 +1942,22 @@ redef class Int end end + # C function to calculate the length of the `NativeString` to receive `self` + private fun int_to_s_len: Int is extern "native_int_length_str" + # C function to convert an nit Int to a NativeString (char*) - private fun native_int_to_s: NativeString is extern "native_int_to_s" + private fun native_int_to_s(nstr: NativeString, strlen: Int) is extern "native_int_to_s" # return displayable int in base 10 and signed # # assert 1.to_s == "1" # assert (-123).to_s == "-123" redef fun to_s do - return native_int_to_s.to_s + var nslen = int_to_s_len + var ns = new NativeString(nslen + 1) + ns[nslen] = '\0' + native_int_to_s(ns, nslen + 1) + return ns.to_s_with_length(nslen) end # return displayable int in hexadecimal @@ -1956,23 +2049,6 @@ redef class Float return p1 + "." + p2 end - - # `self` representation with `nb` digits after the '.'. - # - # assert 12.345.to_precision_native(1) == "12.3" - # assert 12.345.to_precision_native(2) == "12.35" - # assert 12.345.to_precision_native(3) == "12.345" - # assert 12.345.to_precision_native(4) == "12.3450" - fun to_precision_native(nb: Int): String import NativeString.to_s `{ - int size; - char *str; - - size = snprintf(NULL, 0, "%.*f", (int)nb, recv); - str = malloc(size + 1); - sprintf(str, "%.*f", (int)nb, recv ); - - return NativeString_to_s( str ); - `} end redef class Char @@ -2123,7 +2199,7 @@ redef class Map[K,V] var i = iterator var k = i.key var e = i.item - s.append("{k}{couple_sep}{e or else ""}") + s.append("{k or else ""}{couple_sep}{e or else ""}") # Concat other items i.next @@ -2131,7 +2207,7 @@ redef class Map[K,V] s.append(sep) k = i.key e = i.item - s.append("{k}{couple_sep}{e or else ""}") + s.append("{k or else ""}{couple_sep}{e or else ""}") i.next end return s.to_s