1##
2# String
3#
4# ISO 15.2.10
5class String
6  include Comparable
7  ##
8  # Calls the given block for each line
9  # and pass the respective line.
10  #
11  # ISO 15.2.10.5.15
12  def each_line(rs = "\n", &block)
13    return to_enum(:each_line, rs, &block) unless block
14    return block.call(self) if rs.nil?
15    rs.__to_str
16    offset = 0
17    rs_len = rs.length
18    this = dup
19    while pos = this.index(rs, offset)
20      block.call(this[offset, pos + rs_len - offset])
21      offset = pos + rs_len
22    end
23    block.call(this[offset, this.size - offset]) if this.size > offset
24    self
25  end
26
27  # private method for gsub/sub
28  def __sub_replace(pre, m, post)
29    s = ""
30    i = 0
31    while j = index("\\", i)
32      break if j == length-1
33      t = case self[j+1]
34          when "\\"
35            "\\"
36          when "`"
37            pre
38          when "&", "0"
39            m
40          when "'"
41            post
42          when "1", "2", "3", "4", "5", "6", "7", "8", "9"
43            ""
44          else
45            self[j, 2]
46          end
47      s += self[i, j-i] + t
48      i = j + 2
49    end
50    s + self[i, length-i]
51  end
52
53  ##
54  # Replace all matches of +pattern+ with +replacement+.
55  # Call block (if given) for each match and replace
56  # +pattern+ with the value of the block. Return the
57  # final value.
58  #
59  # ISO 15.2.10.5.18
60  def gsub(*args, &block)
61    return to_enum(:gsub, *args) if args.length == 1 && !block
62    raise ArgumentError, "wrong number of arguments" unless (1..2).include?(args.length)
63
64    pattern, replace = *args
65    plen = pattern.length
66    if args.length == 2 && block
67      block = nil
68    end
69    if !replace.nil? || !block
70      replace.__to_str
71    end
72    offset = 0
73    result = []
74    while found = index(pattern, offset)
75      result << self[offset, found - offset]
76      offset = found + plen
77      result << if block
78        block.call(pattern).to_s
79      else
80        replace.__sub_replace(self[0, found], pattern, self[offset..-1] || "")
81      end
82      if plen == 0
83        result << self[offset, 1]
84        offset += 1
85      end
86    end
87    result << self[offset..-1] if offset < length
88    result.join
89  end
90
91  ##
92  # Replace all matches of +pattern+ with +replacement+.
93  # Call block (if given) for each match and replace
94  # +pattern+ with the value of the block. Modify
95  # +self+ with the final value.
96  #
97  # ISO 15.2.10.5.19
98  def gsub!(*args, &block)
99    raise FrozenError, "can't modify frozen String" if frozen?
100    return to_enum(:gsub!, *args) if args.length == 1 && !block
101    str = self.gsub(*args, &block)
102    return nil unless self.index(args[0])
103    self.replace(str)
104  end
105
106  ##
107  # Calls the given block for each match of +pattern+
108  # If no block is given return an array with all
109  # matches of +pattern+.
110  #
111  # ISO 15.2.10.5.32
112  def scan(reg, &block)
113    ### *** TODO *** ###
114    unless Object.const_defined?(:Regexp)
115      raise NotImplementedError, "scan not available (yet)"
116    end
117  end
118
119  ##
120  # Replace only the first match of +pattern+ with
121  # +replacement+. Call block (if given) for each
122  # match and replace +pattern+ with the value of the
123  # block. Return the final value.
124  #
125  # ISO 15.2.10.5.36
126  def sub(*args, &block)
127    unless (1..2).include?(args.length)
128      raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 2)"
129    end
130
131    pattern, replace = *args
132    pattern.__to_str
133    if args.length == 2 && block
134      block = nil
135    end
136    unless block
137      replace.__to_str
138    end
139    result = []
140    this = dup
141    found = index(pattern)
142    return this unless found
143    result << this[0, found]
144    offset = found + pattern.length
145    result << if block
146      block.call(pattern).to_s
147    else
148      replace.__sub_replace(this[0, found], pattern, this[offset..-1] || "")
149    end
150    result << this[offset..-1] if offset < length
151    result.join
152  end
153
154  ##
155  # Replace only the first match of +pattern+ with
156  # +replacement+. Call block (if given) for each
157  # match and replace +pattern+ with the value of the
158  # block. Modify +self+ with the final value.
159  #
160  # ISO 15.2.10.5.37
161  def sub!(*args, &block)
162    raise FrozenError, "can't modify frozen String" if frozen?
163    str = self.sub(*args, &block)
164    return nil unless self.index(args[0])
165    self.replace(str)
166  end
167
168  ##
169  # Call the given block for each character of
170  # +self+.
171  def each_char(&block)
172    pos = 0
173    while pos < self.size
174      block.call(self[pos])
175      pos += 1
176    end
177    self
178  end
179
180  ##
181  # Call the given block for each byte of +self+.
182  def each_byte(&block)
183    bytes = self.bytes
184    pos = 0
185    while pos < bytes.size
186      block.call(bytes[pos])
187      pos += 1
188    end
189    self
190  end
191
192  ##
193  # Modify +self+ by replacing the content of +self+.
194  # The portion of the string affected is determined using the same criteria as +String#[]+.
195  def []=(*args)
196    anum = args.size
197    if anum == 2
198      pos, value = args
199      case pos
200      when String
201        posnum = self.index(pos)
202        if posnum
203          b = self[0, posnum.to_i]
204          a = self[(posnum + pos.length)..-1]
205          self.replace([b, value, a].join(''))
206        else
207          raise IndexError, "string not matched"
208        end
209      when Range
210        head = pos.begin
211        tail = pos.end
212        tail += self.length if tail < 0
213        unless pos.exclude_end?
214          tail += 1
215        end
216        return self[head, tail-head]=value
217      else
218        pos += self.length if pos < 0
219        if pos < 0 || pos > self.length
220          raise IndexError, "index #{args[0]} out of string"
221        end
222        b = self[0, pos.to_i]
223        a = self[pos + 1..-1]
224        self.replace([b, value, a].join(''))
225      end
226      return value
227    elsif anum == 3
228      pos, len, value = args
229      pos += self.length if pos < 0
230      if pos < 0 || pos > self.length
231        raise IndexError, "index #{args[0]} out of string"
232      end
233      if len < 0
234        raise IndexError, "negative length #{len}"
235      end
236      b = self[0, pos.to_i]
237      a = self[pos + len..-1]
238      self.replace([b, value, a].join(''))
239      return value
240    else
241      raise ArgumentError, "wrong number of arguments (#{anum} for 2..3)"
242    end
243  end
244
245  ##
246  # ISO 15.2.10.5.3
247  def =~(re)
248    re =~ self
249  end
250
251  ##
252  # ISO 15.2.10.5.27
253  def match(re, &block)
254    if String === re
255      if Object.const_defined?(:Regexp)
256        r = Regexp.new(re)
257        r.match(self, &block)
258      else
259        raise NotImplementedError, "String#match needs Regexp class"
260      end
261    else
262      re.match(self, &block)
263    end
264  end
265end
266
267##
268# String is comparable
269#
270# ISO 15.2.10.3
271module Comparable; end
272class String
273  include Comparable
274end
275