opal 0.7.2 → 0.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +6 -1
  3. data/CHANGELOG.md +29 -0
  4. data/CONTRIBUTING.md +51 -4
  5. data/Gemfile +3 -0
  6. data/README.md +5 -5
  7. data/config.ru +1 -1
  8. data/examples/sinatra/Gemfile +1 -0
  9. data/examples/sinatra/config.ru +13 -3
  10. data/lib/mspec/opal/rake_task.rb +21 -30
  11. data/lib/mspec/opal/runner.rb +37 -0
  12. data/lib/mspec/opal/special_calls.rb +6 -0
  13. data/lib/opal/builder.rb +1 -0
  14. data/lib/opal/builder_processors.rb +5 -2
  15. data/lib/opal/cli_runners/phantom.js +10 -1
  16. data/lib/opal/compiler.rb +6 -3
  17. data/lib/opal/config.rb +48 -0
  18. data/lib/opal/nodes/call.rb +3 -2
  19. data/lib/opal/nodes/literal.rb +19 -2
  20. data/lib/opal/parser/grammar.rb +2224 -2196
  21. data/lib/opal/parser/grammar.y +25 -7
  22. data/lib/opal/parser/lexer.rb +12 -9
  23. data/lib/opal/path_reader.rb +1 -1
  24. data/lib/opal/sprockets/erb.rb +6 -20
  25. data/lib/opal/sprockets/path_reader.rb +4 -2
  26. data/lib/opal/sprockets/processor.rb +135 -80
  27. data/lib/opal/sprockets/server.rb +49 -78
  28. data/lib/opal/sprockets/source_map_header_patch.rb +41 -0
  29. data/lib/opal/sprockets/source_map_server.rb +115 -0
  30. data/lib/opal/version.rb +1 -1
  31. data/lib/tilt/opal.rb +48 -0
  32. data/opal.gemspec +1 -1
  33. data/opal/corelib/array.rb +179 -51
  34. data/opal/corelib/array/inheritance.rb +14 -0
  35. data/opal/corelib/boolean.rb +5 -0
  36. data/opal/corelib/hash.rb +1 -1
  37. data/opal/corelib/kernel.rb +660 -164
  38. data/opal/corelib/match_data.rb +44 -21
  39. data/opal/corelib/module.rb +83 -53
  40. data/opal/corelib/numeric.rb +15 -1
  41. data/opal/corelib/regexp.rb +31 -75
  42. data/opal/corelib/runtime.js +20 -8
  43. data/opal/corelib/string.rb +754 -243
  44. data/opal/corelib/string/inheritance.rb +20 -3
  45. data/opal/corelib/struct.rb +30 -6
  46. data/opal/corelib/variables.rb +2 -2
  47. data/spec/filters/bugs/array.rb +0 -39
  48. data/spec/filters/bugs/kernel.rb +10 -7
  49. data/spec/filters/bugs/module.rb +21 -0
  50. data/spec/filters/bugs/opal.rb +0 -5
  51. data/spec/filters/bugs/singleton.rb +0 -2
  52. data/spec/filters/bugs/string.rb +69 -315
  53. data/spec/filters/bugs/struct.rb +0 -16
  54. data/spec/filters/unsupported/encoding.rb +7 -0
  55. data/spec/filters/unsupported/float.rb +3 -0
  56. data/spec/filters/unsupported/integer_size.rb +52 -0
  57. data/spec/filters/unsupported/marshal.rb +4 -0
  58. data/spec/filters/unsupported/mutable_strings.rb +37 -0
  59. data/spec/filters/unsupported/private_methods.rb +11 -0
  60. data/spec/filters/unsupported/rational_numbers.rb +4 -0
  61. data/spec/filters/unsupported/regular_expressions.rb +47 -0
  62. data/spec/filters/unsupported/symbols.rb +7 -0
  63. data/spec/filters/unsupported/tainted.rb +23 -1
  64. data/spec/filters/unsupported/trusted.rb +5 -0
  65. data/spec/lib/fixtures/complex_sprockets.js.rb.erb +4 -0
  66. data/spec/lib/fixtures/jst_file.js.jst +1 -0
  67. data/spec/lib/parser/call_spec.rb +19 -0
  68. data/spec/lib/parser/def_spec.rb +6 -0
  69. data/spec/lib/sprockets/erb_spec.rb +17 -4
  70. data/spec/lib/sprockets/processor_spec.rb +50 -18
  71. data/spec/lib/sprockets/server_spec.rb +39 -11
  72. data/spec/lib/tilt/opal_spec.rb +19 -0
  73. data/spec/opal/core/kernel/format_spec.rb +10 -10
  74. data/spec/opal/core/language/predefined_spec.rb +10 -0
  75. data/spec/opal/core/object_id_spec.rb +56 -0
  76. data/spec/opal/core/runtime/bridged_classes_spec.rb +4 -4
  77. data/spec/opal/core/runtime_spec.rb +7 -0
  78. data/spec/opal/stdlib/native/native_class_spec.rb +1 -1
  79. data/spec/opal/stdlib/promise/always_spec.rb +30 -0
  80. data/spec/opal/stdlib/promise/then_spec.rb +8 -0
  81. data/spec/opal/stdlib/promise/trace_spec.rb +8 -0
  82. data/spec/rubyspecs +15 -104
  83. data/spec/spec_helper.rb +4 -0
  84. data/stdlib/native.rb +7 -18
  85. data/stdlib/nodejs/file.rb +1 -1
  86. data/stdlib/opal-parser.rb +1 -0
  87. data/stdlib/pp.rb +7 -5
  88. data/stdlib/promise.rb +53 -41
  89. data/tasks/testing.rake +8 -6
  90. metadata +28 -14
  91. data/spec/filters/bugs/match_data.rb +0 -13
  92. data/spec/filters/bugs/numeric.rb +0 -22
  93. data/spec/filters/bugs/regexp.rb +0 -9
  94. data/spec/filters/bugs/unknown.rb +0 -11
@@ -29,12 +29,17 @@
29
29
  var $hasOwn = Opal.hasOwnProperty;
30
30
  var $slice = Opal.slice = Array.prototype.slice;
31
31
 
32
- // Generates unique id for every ruby object
33
- var unique_id = 0;
32
+ // Nil object id is always 4
33
+ var nil_id = 4;
34
+
35
+ // Generates even sequential numbers greater than 4
36
+ // (nil_id) to serve as unique ids for ruby objects
37
+ var unique_id = nil_id;
34
38
 
35
39
  // Return next unique id
36
40
  Opal.uid = function() {
37
- return unique_id++;
41
+ unique_id += 2;
42
+ return unique_id;
38
43
  };
39
44
 
40
45
  // Table holds all class variables
@@ -238,7 +243,7 @@
238
243
  function setup_module_or_class_object(module, constructor, superklass, prototype) {
239
244
  // @property $$id Each class is assigned a unique `id` that helps
240
245
  // comparation and implementation of `#object_id`
241
- module.$$id = unique_id++;
246
+ module.$$id = Opal.uid();
242
247
 
243
248
  // @property $$proto This is the prototype on which methods will be defined
244
249
  module.$$proto = prototype;
@@ -1134,6 +1139,13 @@
1134
1139
  }
1135
1140
  }
1136
1141
 
1142
+ /*
1143
+ * Called to remove a method.
1144
+ */
1145
+ Opal.undef = function(obj, jsid) {
1146
+ delete obj.$$proto[jsid];
1147
+ };
1148
+
1137
1149
  Opal.hash = function() {
1138
1150
  if (arguments.length == 1 && arguments[0].$$class == Opal.Hash) {
1139
1151
  return arguments[0];
@@ -1182,7 +1194,7 @@
1182
1194
  obj = arguments[0];
1183
1195
  for (key in obj) {
1184
1196
  khash = key.$hash();
1185
- map[khash] = obj[khash];
1197
+ smap[khash] = obj[khash];
1186
1198
  keys.push(key);
1187
1199
  }
1188
1200
  }
@@ -1248,8 +1260,8 @@
1248
1260
  // Require system
1249
1261
  // --------------
1250
1262
  (function(Opal) {
1251
- var loaded_features = ['corelib/runtime.js'],
1252
- require_table = {'corelib/runtime.js': true},
1263
+ var loaded_features = ['corelib/runtime'],
1264
+ require_table = {'corelib/runtime': true},
1253
1265
  modules = {};
1254
1266
 
1255
1267
  var current_dir = '.';
@@ -1272,6 +1284,7 @@
1272
1284
  path = current_dir.replace(/\/*$/, '/') + path;
1273
1285
  }
1274
1286
 
1287
+ path = path.replace(/\.(rb|opal|js)$/, '');
1275
1288
  parts = path.split(SEPARATOR);
1276
1289
 
1277
1290
  for (var i = 0, ii = parts.length; i < ii; i++) {
@@ -1408,7 +1421,6 @@
1408
1421
  Opal.top = new ObjectClass.$$alloc();
1409
1422
 
1410
1423
  // Nil
1411
- var nil_id = Opal.uid(); // nil id is traditionally 4
1412
1424
  Opal.klass(ObjectClass, ObjectClass, 'NilClass', NilClass);
1413
1425
  var nil = Opal.nil = new NilClass();
1414
1426
  nil.$$id = nil_id;
@@ -5,16 +5,29 @@ class String
5
5
 
6
6
  `def.$$is_string = true`
7
7
 
8
+ def __id__
9
+ `self.toString()`
10
+ end
11
+ alias object_id __id__
12
+
8
13
  def self.try_convert(what)
9
- what.to_str
10
- rescue
11
- nil
14
+ Opal.coerce_to?(what, String, :to_str)
12
15
  end
13
16
 
14
17
  def self.new(str = '')
18
+ str = Opal.coerce_to(str, String, :to_str)
15
19
  `new String(str)`
16
20
  end
17
21
 
22
+ def initialize(str = undefined)
23
+ %x{
24
+ if (str === undefined) {
25
+ return self;
26
+ }
27
+ }
28
+ raise NotImplementedError, 'Mutable strings are not supported in Opal.'
29
+ end
30
+
18
31
  def %(data)
19
32
  if Array === data
20
33
  format(self, *data)
@@ -25,20 +38,36 @@ class String
25
38
 
26
39
  def *(count)
27
40
  %x{
28
- if (count < 1) {
41
+ count = #{Opal.coerce_to(`count`, Integer, :to_int)};
42
+
43
+ if (count < 0) {
44
+ #{raise ArgumentError, 'negative argument'}
45
+ }
46
+
47
+ if (count === 0) {
29
48
  return '';
30
49
  }
31
50
 
32
- var result = '',
33
- pattern = self;
51
+ var result = '',
52
+ string = self.toString();
34
53
 
35
- while (count > 0) {
36
- if (count & 1) {
37
- result += pattern;
38
- }
54
+ // All credit for the bit-twiddling magic code below goes to Mozilla
55
+ // polyfill implementation of String.prototype.repeat() posted here:
56
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
39
57
 
40
- count >>= 1;
41
- pattern += pattern;
58
+ if (string.length * count >= 1 << 28) {
59
+ #{raise RangeError, 'multiply count must not overflow maximum string size'}
60
+ }
61
+
62
+ for (;;) {
63
+ if ((count & 1) === 1) {
64
+ result += string;
65
+ }
66
+ count >>>= 1;
67
+ if (count === 0) {
68
+ break;
69
+ }
70
+ string += string;
42
71
  }
43
72
 
44
73
  return result;
@@ -75,9 +104,15 @@ class String
75
104
  end
76
105
 
77
106
  def ==(other)
78
- return false unless String === other
79
-
80
- `#{to_s} == #{other.to_s}`
107
+ %x{
108
+ if (other.$$is_string) {
109
+ return self.toString() === other.toString();
110
+ }
111
+ if (#{Opal.respond_to? `other`, :to_str}) {
112
+ return #{other == self};
113
+ }
114
+ return false;
115
+ }
81
116
  end
82
117
 
83
118
  alias eql? ==
@@ -99,8 +134,12 @@ class String
99
134
 
100
135
  if (index.$$is_range) {
101
136
  var exclude = index.exclude,
102
- length = index.end,
103
- index = index.begin;
137
+ length = #{Opal.coerce_to(`index.end`, Integer, :to_int)},
138
+ index = #{Opal.coerce_to(`index.begin`, Integer, :to_int)};
139
+
140
+ if (Math.abs(index) > size) {
141
+ return nil;
142
+ }
104
143
 
105
144
  if (index < 0) {
106
145
  index += size;
@@ -114,10 +153,6 @@ class String
114
153
  length += 1;
115
154
  }
116
155
 
117
- if (index > size) {
118
- return nil;
119
- }
120
-
121
156
  length = length - index;
122
157
 
123
158
  if (length < 0) {
@@ -127,19 +162,63 @@ class String
127
162
  return self.substr(index, length);
128
163
  }
129
164
 
165
+
166
+ if (index.$$is_string) {
167
+ if (length != null) {
168
+ #{raise TypeError}
169
+ }
170
+ return self.indexOf(index) !== -1 ? index : nil;
171
+ }
172
+
173
+
174
+ if (index.$$is_regexp) {
175
+ var match = self.match(index);
176
+
177
+ if (match === null) {
178
+ #{$~ = nil}
179
+ return nil;
180
+ }
181
+
182
+ #{$~ = MatchData.new(`index`, `match`)}
183
+
184
+ if (length == null) {
185
+ return match[0];
186
+ }
187
+
188
+ length = #{Opal.coerce_to(`length`, Integer, :to_int)};
189
+
190
+ if (length < 0 && -length < match.length) {
191
+ return match[length += match.length];
192
+ }
193
+
194
+ if (length >= 0 && length < match.length) {
195
+ return match[length];
196
+ }
197
+
198
+ return nil;
199
+ }
200
+
201
+
202
+ index = #{Opal.coerce_to(`index`, Integer, :to_int)};
203
+
130
204
  if (index < 0) {
131
- index += self.length;
205
+ index += size;
132
206
  }
133
207
 
134
208
  if (length == null) {
135
- if (index >= self.length || index < 0) {
209
+ if (index >= size || index < 0) {
136
210
  return nil;
137
211
  }
138
-
139
212
  return self.substr(index, 1);
140
213
  }
141
214
 
142
- if (index > self.length || index < 0) {
215
+ length = #{Opal.coerce_to(`length`, Integer, :to_int)};
216
+
217
+ if (length < 0) {
218
+ return nil;
219
+ }
220
+
221
+ if (index > size || index < 0) {
143
222
  return nil;
144
223
  }
145
224
 
@@ -155,8 +234,14 @@ class String
155
234
 
156
235
  def casecmp(other)
157
236
  other = Opal.coerce_to(other, String, :to_str).to_s
158
-
159
- `self.toLowerCase()` <=> `other.toLowerCase()`
237
+ %x{
238
+ var ascii_only = /^[\x00-\x7F]*$/;
239
+ if (ascii_only.test(self) && ascii_only.test(other)) {
240
+ self = self.toLowerCase();
241
+ other = other.toLowerCase();
242
+ }
243
+ }
244
+ self <=> other
160
245
  end
161
246
 
162
247
  def center(width, padstr = ' ')
@@ -244,8 +329,30 @@ class String
244
329
  copy
245
330
  end
246
331
 
247
- def count(str)
248
- `(self.length - self.replace(new RegExp(str, 'g'), '').length) / str.length`
332
+ def count(*sets)
333
+ %x{
334
+ if (sets.length === 0) {
335
+ #{raise ArgumentError, "ArgumentError: wrong number of arguments (0 for 1+)"}
336
+ }
337
+ var char_class = char_class_from_char_sets(sets);
338
+ if (char_class === null) {
339
+ return 0;
340
+ }
341
+ return self.length - self.replace(new RegExp(char_class, 'g'), '').length;
342
+ }
343
+ end
344
+
345
+ def delete(*sets)
346
+ %x{
347
+ if (sets.length === 0) {
348
+ #{raise ArgumentError, "ArgumentError: wrong number of arguments (0 for 1+)"}
349
+ }
350
+ var char_class = char_class_from_char_sets(sets);
351
+ if (char_class === null) {
352
+ return self;
353
+ }
354
+ return self.replace(new RegExp(char_class, 'g'), '');
355
+ }
249
356
  end
250
357
 
251
358
  alias dup clone
@@ -269,10 +376,26 @@ class String
269
376
  end
270
377
 
271
378
  def each_line(separator = $/)
272
- return split(separator) unless block_given?
379
+ return enum_for :each_line, separator unless block_given?
273
380
 
274
381
  %x{
275
- var chomped = #{chomp},
382
+ if (separator === nil) {
383
+ #{yield self};
384
+ return self;
385
+ }
386
+
387
+ separator = #{Opal.coerce_to(`separator`, String, :to_str)}
388
+
389
+ if (separator.length === 0) {
390
+ for (var a = self.split(/(\n{2,})/), i = 0, n = a.length; i < n; i += 2) {
391
+ if (a[i] || a[i + 1]) {
392
+ #{yield `(a[i] || "") + (a[i + 1] || "")`};
393
+ }
394
+ }
395
+ return self;
396
+ }
397
+
398
+ var chomped = #{chomp(separator)},
276
399
  trailing = self.length != chomped.length,
277
400
  splitted = chomped.split(separator);
278
401
 
@@ -311,22 +434,73 @@ class String
311
434
  alias eql? ==
312
435
  alias equal? ===
313
436
 
314
- def gsub(pattern, replace = undefined, &block)
315
- if String === pattern || pattern.respond_to?(:to_str)
316
- pattern = /#{Regexp.escape(pattern.to_str)}/
317
- end
437
+ def gsub(pattern, replacement = undefined, &block)
438
+ %x{
439
+ var result = '', match_data = nil, index = 0, match, _replacement;
318
440
 
319
- unless Regexp === pattern
320
- raise TypeError, "wrong argument type #{pattern.class} (expected Regexp)"
321
- end
441
+ if (pattern.$$is_regexp) {
442
+ pattern = new RegExp(pattern.source, 'gm' + (pattern.ignoreCase ? 'i' : ''));
443
+ } else {
444
+ pattern = #{Opal.coerce_to(`pattern`, String, :to_str)};
445
+ pattern = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gm');
446
+ }
322
447
 
323
- %x{
324
- var pattern = pattern.toString(),
325
- options = pattern.substr(pattern.lastIndexOf('/') + 1) + 'g',
326
- regexp = pattern.substr(1, pattern.lastIndexOf('/') - 1);
448
+ while (true) {
449
+ match = pattern.exec(self);
450
+
451
+ if (match === null) {
452
+ #{$~ = nil}
453
+ result += self.slice(index);
454
+ break;
455
+ }
456
+
457
+ match_data = #{MatchData.new `pattern`, `match`};
458
+
459
+ if (replacement === undefined) {
460
+ if (block === nil) {
461
+ #{raise ArgumentError, 'wrong number of arguments (1 for 2)'}
462
+ }
463
+ _replacement = block(match[0]);
464
+ }
465
+ else if (replacement.$$is_hash) {
466
+ _replacement = #{`replacement`[`match[0]`].to_s};
467
+ }
468
+ else {
469
+ if (!replacement.$$is_string) {
470
+ replacement = #{Opal.coerce_to(`replacement`, String, :to_str)};
471
+ }
472
+ _replacement = replacement.replace(/([\\]+)([0-9+&`'])/g, function (original, slashes, command) {
473
+ if (slashes.length % 2 === 0) {
474
+ return original;
475
+ }
476
+ switch (command) {
477
+ case "+":
478
+ for (var i = match.length - 1; i > 0; i--) {
479
+ if (match[i] !== undefined) {
480
+ return slashes.slice(1) + match[i];
481
+ }
482
+ }
483
+ return '';
484
+ case "&": return slashes.slice(1) + match[0];
485
+ case "`": return slashes.slice(1) + self.slice(0, match.index);
486
+ case "'": return slashes.slice(1) + self.slice(match.index + match[0].length);
487
+ default: return slashes.slice(1) + (match[command] || '');
488
+ }
489
+ }).replace(/\\\\/g, '\\');
490
+ }
491
+
492
+ if (pattern.lastIndex === match.index) {
493
+ result += (_replacement + self.slice(index, match.index + 1))
494
+ pattern.lastIndex += 1;
495
+ }
496
+ else {
497
+ result += (self.slice(index, match.index) + _replacement)
498
+ }
499
+ index = pattern.lastIndex;
500
+ }
327
501
 
328
- self.$sub.$$p = block;
329
- return self.$sub(new RegExp(regexp, options), replace);
502
+ #{$~ = `match_data`}
503
+ return result;
330
504
  }
331
505
  end
332
506
 
@@ -354,77 +528,72 @@ class String
354
528
  `self.indexOf(#{other.to_str}) !== -1`
355
529
  end
356
530
 
357
- def index(what, offset = nil)
358
- if String === what
359
- what = what.to_s
360
- elsif what.respond_to? :to_str
361
- what = what.to_str.to_s
362
- elsif not Regexp === what
363
- raise TypeError, "type mismatch: #{what.class} given"
364
- end
365
-
366
- result = -1
367
-
368
- if offset
369
- offset = Opal.coerce_to offset, Integer, :to_int
370
-
371
- %x{
372
- var size = self.length;
373
-
531
+ def index(search, offset = undefined)
532
+ %x{
533
+ var index,
534
+ match,
535
+ regex;
536
+
537
+ if (offset === undefined) {
538
+ offset = 0;
539
+ } else {
540
+ offset = #{Opal.coerce_to(`offset`, Integer, :to_int)};
374
541
  if (offset < 0) {
375
- offset = offset + size;
376
- }
377
-
378
- if (offset > size) {
379
- return nil;
542
+ offset += self.length;
543
+ if (offset < 0) {
544
+ return nil;
545
+ }
380
546
  }
381
547
  }
382
548
 
383
- if Regexp === what
384
- result = (what =~ `self.substr(offset)`) || -1
385
- else
386
- result = `self.substr(offset).indexOf(what)`
387
- end
388
-
389
- %x{
390
- if (result !== -1) {
391
- result += offset;
549
+ if (search.$$is_regexp) {
550
+ regex = new RegExp(search.source, 'gm' + (search.ignoreCase ? 'i' : ''));
551
+ while (true) {
552
+ match = regex.exec(self);
553
+ if (match === null) {
554
+ #{$~ = nil};
555
+ index = -1;
556
+ break;
557
+ }
558
+ if (match.index >= offset) {
559
+ #{$~ = MatchData.new(`regex`, `match`)}
560
+ index = match.index;
561
+ break;
562
+ }
563
+ regex.lastIndex = match.index + 1;
564
+ }
565
+ } else {
566
+ search = #{Opal.coerce_to(`search`, String, :to_str)};
567
+ if (search.length === 0 && offset > self.length) {
568
+ index = -1;
569
+ } else {
570
+ index = self.indexOf(search, offset);
392
571
  }
393
572
  }
394
- else
395
- if Regexp === what
396
- result = (what =~ self) || -1
397
- else
398
- result = `self.indexOf(what)`
399
- end
400
- end
401
573
 
402
- unless `result === -1`
403
- result
404
- end
574
+ return index === -1 ? nil : index;
575
+ }
405
576
  end
406
577
 
407
578
  def inspect
408
579
  %x{
409
- var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
410
- meta = {
580
+ var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
581
+ meta = {
582
+ '\u0007': '\\a',
583
+ '\u001b': '\\e',
411
584
  '\b': '\\b',
412
585
  '\t': '\\t',
413
586
  '\n': '\\n',
414
587
  '\f': '\\f',
415
588
  '\r': '\\r',
589
+ '\v': '\\v',
416
590
  '"' : '\\"',
417
591
  '\\': '\\\\'
418
- };
419
-
420
- escapable.lastIndex = 0;
421
-
422
- return escapable.test(self) ? '"' + self.replace(escapable, function(a) {
423
- var c = meta[a];
424
-
425
- return typeof c === 'string' ? c :
426
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
427
- }) + '"' : '"' + self + '"';
592
+ },
593
+ escaped = self.replace(escapable, function (chr) {
594
+ return meta[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16).toUpperCase()).slice(-4);
595
+ });
596
+ return '"' + escaped.replace(/\#[\$\@\{]/g, '\\$&') + '"';
428
597
  }
429
598
  end
430
599
 
@@ -432,8 +601,9 @@ class String
432
601
  self
433
602
  end
434
603
 
435
- def lines(separator = $/)
436
- each_line(separator).to_a
604
+ def lines(separator = $/, &block)
605
+ e = each_line(separator, &block)
606
+ block ? self : e.to_a
437
607
  end
438
608
 
439
609
  def length
@@ -472,7 +642,7 @@ class String
472
642
 
473
643
  def match(pattern, pos = undefined, &block)
474
644
  if String === pattern || pattern.respond_to?(:to_str)
475
- pattern = /#{Regexp.escape(pattern.to_str)}/
645
+ pattern = Regexp.new(pattern.to_str)
476
646
  end
477
647
 
478
648
  unless Regexp === pattern
@@ -484,29 +654,150 @@ class String
484
654
 
485
655
  def next
486
656
  %x{
487
- if (self.length === 0) {
488
- return "";
657
+ var i = self.length;
658
+ if (i === 0) {
659
+ return '';
489
660
  }
490
-
491
- var initial = self.substr(0, self.length - 1);
492
- var last = String.fromCharCode(self.charCodeAt(self.length - 1) + 1);
493
-
494
- return initial + last;
661
+ var result = self;
662
+ var first_alphanum_char_index = self.search(/[a-zA-Z0-9]/);
663
+ var carry = false;
664
+ var code;
665
+ while (i--) {
666
+ code = self.charCodeAt(i);
667
+ if ((code >= 48 && code <= 57) ||
668
+ (code >= 65 && code <= 90) ||
669
+ (code >= 97 && code <= 122)) {
670
+ switch (code) {
671
+ case 57:
672
+ carry = true;
673
+ code = 48;
674
+ break;
675
+ case 90:
676
+ carry = true;
677
+ code = 65;
678
+ break;
679
+ case 122:
680
+ carry = true;
681
+ code = 97;
682
+ break;
683
+ default:
684
+ carry = false;
685
+ code += 1;
686
+ }
687
+ } else {
688
+ if (first_alphanum_char_index === -1) {
689
+ if (code === 255) {
690
+ carry = true;
691
+ code = 0;
692
+ } else {
693
+ carry = false;
694
+ code += 1;
695
+ }
696
+ } else {
697
+ carry = true;
698
+ }
699
+ }
700
+ result = result.slice(0, i) + String.fromCharCode(code) + result.slice(i + 1);
701
+ if (carry && (i === 0 || i === first_alphanum_char_index)) {
702
+ switch (code) {
703
+ case 65:
704
+ break;
705
+ case 97:
706
+ break;
707
+ default:
708
+ code += 1;
709
+ }
710
+ if (i === 0) {
711
+ result = String.fromCharCode(code) + result;
712
+ } else {
713
+ result = result.slice(0, i) + String.fromCharCode(code) + result.slice(i);
714
+ }
715
+ carry = false;
716
+ }
717
+ if (!carry) {
718
+ break;
719
+ }
720
+ }
721
+ return result;
495
722
  }
496
723
  end
497
724
 
498
725
  alias next! <<
499
726
 
727
+ def oct
728
+ %x{
729
+ var result,
730
+ string = self,
731
+ radix = 8;
732
+
733
+ if (/^\s*_/.test(string)) {
734
+ return 0;
735
+ }
736
+
737
+ string = string.replace(/^(\s*[+-]?)(0[bodx]?)(.+)$/i, function (original, head, flag, tail) {
738
+ switch (tail.charAt(0)) {
739
+ case '+':
740
+ case '-':
741
+ return original;
742
+ case '0':
743
+ if (tail.charAt(1) === 'x' && flag === '0x') {
744
+ return original;
745
+ }
746
+ }
747
+ switch (flag) {
748
+ case '0b':
749
+ radix = 2;
750
+ break;
751
+ case '0':
752
+ case '0o':
753
+ radix = 8;
754
+ break;
755
+ case '0d':
756
+ radix = 10;
757
+ break;
758
+ case '0x':
759
+ radix = 16;
760
+ break;
761
+ }
762
+ return head + tail;
763
+ });
764
+
765
+ result = parseInt(string.replace(/_(?!_)/g, ''), radix);
766
+ return isNaN(result) ? 0 : result;
767
+ }
768
+ end
769
+
500
770
  def ord
501
771
  `self.charCodeAt(0)`
502
772
  end
503
773
 
504
- def partition(str)
774
+ def partition(sep)
505
775
  %x{
506
- var result = self.split(str);
507
- var splitter = (result[0].length === self.length ? "" : str);
776
+ var i, m;
777
+
778
+ if (sep.$$is_regexp) {
779
+ m = sep.exec(self);
780
+ if (m === null) {
781
+ i = -1;
782
+ } else {
783
+ #{MatchData.new `sep`, `m`};
784
+ sep = m[0];
785
+ i = m.index;
786
+ }
787
+ } else {
788
+ sep = #{Opal.coerce_to(`sep`, String, :to_str)};
789
+ i = self.indexOf(sep);
790
+ }
508
791
 
509
- return [result[0], splitter, result.slice(1).join(str.toString())];
792
+ if (i === -1) {
793
+ return [self, '', ''];
794
+ }
795
+
796
+ return [
797
+ self.slice(0, i),
798
+ self.slice(i, i + sep.length),
799
+ self.slice(i + sep.length)
800
+ ];
510
801
  }
511
802
  end
512
803
 
@@ -516,48 +807,46 @@ class String
516
807
 
517
808
  alias reverse! <<
518
809
 
519
- # TODO handle case where search is regexp
520
810
  def rindex(search, offset = undefined)
521
811
  %x{
522
- var search_type = (search == null ? Opal.NilClass : search.constructor);
523
- if (search_type != String && search_type != RegExp) {
524
- var msg = "type mismatch: " + search_type + " given";
525
- #{raise TypeError.new(`msg`)};
526
- }
527
-
528
- if (self.length == 0) {
529
- return search.length == 0 ? 0 : nil;
530
- }
812
+ var i, m, r, _m;
531
813
 
532
- var result = -1;
533
- if (offset != null) {
814
+ if (offset === undefined) {
815
+ offset = self.length;
816
+ } else {
817
+ offset = #{Opal.coerce_to(`offset`, Integer, :to_int)};
534
818
  if (offset < 0) {
535
- offset = self.length + offset;
536
- }
537
-
538
- if (search_type == String) {
539
- result = self.lastIndexOf(search, offset);
540
- }
541
- else {
542
- result = self.substr(0, offset + 1).$reverse().search(search);
543
- if (result !== -1) {
544
- result = offset - result;
819
+ offset += self.length;
820
+ if (offset < 0) {
821
+ return nil;
545
822
  }
546
823
  }
547
824
  }
548
- else {
549
- if (search_type == String) {
550
- result = self.lastIndexOf(search);
551
- }
552
- else {
553
- result = self.$reverse().search(search);
554
- if (result !== -1) {
555
- result = self.length - 1 - result;
825
+
826
+ if (search.$$is_regexp) {
827
+ m = null;
828
+ r = new RegExp(search.source, 'gm' + (search.ignoreCase ? 'i' : ''));
829
+ while (true) {
830
+ _m = r.exec(self);
831
+ if (_m === null || _m.index > offset) {
832
+ break;
556
833
  }
834
+ m = _m;
835
+ r.lastIndex = m.index + 1;
836
+ }
837
+ if (m === null) {
838
+ #{$~ = nil}
839
+ i = -1;
840
+ } else {
841
+ #{MatchData.new `r`, `m`};
842
+ i = m.index;
557
843
  }
844
+ } else {
845
+ search = #{Opal.coerce_to(`search`, String, :to_str)};
846
+ i = self.lastIndexOf(search, offset);
558
847
  }
559
848
 
560
- return result === -1 ? nil : result;
849
+ return i === -1 ? nil : i;
561
850
  }
562
851
  end
563
852
 
@@ -581,34 +870,79 @@ class String
581
870
  }
582
871
  end
583
872
 
873
+ def rpartition(sep)
874
+ %x{
875
+ var i, m, r, _m;
876
+
877
+ if (sep.$$is_regexp) {
878
+ m = null;
879
+ r = new RegExp(sep.source, 'gm' + (sep.ignoreCase ? 'i' : ''));
880
+
881
+ while (true) {
882
+ _m = r.exec(self);
883
+ if (_m === null) {
884
+ break;
885
+ }
886
+ m = _m;
887
+ r.lastIndex = m.index + 1;
888
+ }
889
+
890
+ if (m === null) {
891
+ i = -1;
892
+ } else {
893
+ #{MatchData.new `r`, `m`};
894
+ sep = m[0];
895
+ i = m.index;
896
+ }
897
+
898
+ } else {
899
+ sep = #{Opal.coerce_to(`sep`, String, :to_str)};
900
+ i = self.lastIndexOf(sep);
901
+ }
902
+
903
+ if (i === -1) {
904
+ return ['', '', self];
905
+ }
906
+
907
+ return [
908
+ self.slice(0, i),
909
+ self.slice(i, i + sep.length),
910
+ self.slice(i + sep.length)
911
+ ];
912
+ }
913
+ end
914
+
584
915
  def rstrip
585
- `self.replace(/\s*$/, '')`
916
+ `self.replace(/[\s\u0000]*$/, '')`
586
917
  end
587
918
 
588
919
  def scan(pattern, &block)
589
920
  %x{
590
- if (pattern.global) {
591
- // should we clear it afterwards too?
592
- pattern.lastIndex = 0;
593
- }
594
- else {
595
- // rewrite regular expression to add the global flag to capture pre/post match
596
- pattern = new RegExp(pattern.source, 'g' + (pattern.multiline ? 'm' : '') + (pattern.ignoreCase ? 'i' : ''));
921
+ var result = [],
922
+ match_data = nil,
923
+ match;
924
+
925
+ if (pattern.$$is_regexp) {
926
+ pattern = new RegExp(pattern.source, 'gm' + (pattern.ignoreCase ? 'i' : ''));
927
+ } else {
928
+ pattern = #{Opal.coerce_to(`pattern`, String, :to_str)};
929
+ pattern = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gm');
597
930
  }
598
931
 
599
- var result = [];
600
- var match;
601
-
602
932
  while ((match = pattern.exec(self)) != null) {
603
- var match_data = #{MatchData.new `pattern`, `match`};
933
+ match_data = #{MatchData.new `pattern`, `match`};
604
934
  if (block === nil) {
605
- match.length == 1 ? result.push(match[0]) : result.push(match.slice(1));
935
+ match.length == 1 ? result.push(match[0]) : result.push(#{`match_data`.captures});
936
+ } else {
937
+ match.length == 1 ? block(match[0]) : block.call(self, #{`match_data`.captures});
606
938
  }
607
- else {
608
- match.length == 1 ? block(match[0]) : block.apply(self, match.slice(1));
939
+ if (pattern.lastIndex === match.index) {
940
+ pattern.lastIndex += 1;
609
941
  }
610
942
  }
611
943
 
944
+ #{$~ = `match_data`}
945
+
612
946
  return (block !== nil ? self : result);
613
947
  }
614
948
  end
@@ -748,20 +1082,11 @@ class String
748
1082
  if (sets.length === 0) {
749
1083
  return self.replace(/(.)\1+/g, '$1');
750
1084
  }
751
- }
752
-
753
- %x{
754
- var set = #{Opal.coerce_to(`sets[0]`, String, :to_str).chars};
755
-
756
- for (var i = 1, length = sets.length; i < length; i++) {
757
- set = #{`set` & Opal.coerce_to(`sets[i]`, String, :to_str).chars};
758
- }
759
-
760
- if (set.length === 0) {
1085
+ var char_class = char_class_from_char_sets(sets);
1086
+ if (char_class === null) {
761
1087
  return self;
762
1088
  }
763
-
764
- return self.replace(new RegExp("([" + #{Regexp.escape(`set`.join)} + "])\\1+", "g"), "$1");
1089
+ return self.replace(new RegExp('(' + char_class + ')\\1+', 'g'), '$1');
765
1090
  }
766
1091
  end
767
1092
 
@@ -782,78 +1107,60 @@ class String
782
1107
  end
783
1108
 
784
1109
  def strip
785
- `self.replace(/^\s*/, '').replace(/\s*$/, '')`
1110
+ `self.replace(/^\s*/, '').replace(/[\s\u0000]*$/, '')`
786
1111
  end
787
1112
 
788
1113
  alias strip! <<
789
1114
 
790
- %x{
791
- // convert Ruby back reference to JavaScript back reference
792
- function convertReplace(replace) {
793
- return replace.replace(
794
- /(^|[^\\])\\(\d)/g, function(a, b, c) { return b + '$' + c }
795
- ).replace(
796
- /(^|[^\\])(\\\\)+\\\\(\d)/g, '$1$2\\$3'
797
- ).replace(
798
- /(^|[^\\])(?:(\\)\\)+([^\\]|$)/g, '$1$2$3'
799
- );
800
- }
801
- }
802
-
803
- def sub(pattern, replace = undefined, &block)
1115
+ def sub(pattern, replacement = undefined, &block)
804
1116
  %x{
805
- if (typeof(pattern) !== 'string' && !pattern.$$is_regexp) {
806
- pattern = #{Opal.coerce_to! pattern, String, :to_str};
1117
+ if (!pattern.$$is_regexp) {
1118
+ pattern = #{Opal.coerce_to(`pattern`, String, :to_str)};
1119
+ pattern = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
807
1120
  }
808
1121
 
809
- if (replace !== undefined) {
810
- if (#{replace.is_a?(Hash)}) {
811
- return self.replace(pattern, function(str) {
812
- var value = #{replace[str]};
1122
+ var result = pattern.exec(self);
813
1123
 
814
- return (value == null) ? nil : #{value.to_s};
815
- });
816
- }
817
- else {
818
- if (typeof(replace) !== 'string') {
819
- replace = #{Opal.coerce_to! replace, String, :to_str};
820
- }
1124
+ if (result === null) {
1125
+ #{$~ = nil}
1126
+ return self.toString();
1127
+ }
1128
+
1129
+ #{MatchData.new `pattern`, `result`}
821
1130
 
822
- replace = convertReplace(replace);
823
- return self.replace(pattern, replace);
1131
+ if (replacement === undefined) {
1132
+ if (block === nil) {
1133
+ #{raise ArgumentError, 'wrong number of arguments (1 for 2)'}
824
1134
  }
1135
+ return self.slice(0, result.index) + block(result[0]) + self.slice(result.index + result[0].length);
1136
+ }
825
1137
 
1138
+ if (replacement.$$is_hash) {
1139
+ return self.slice(0, result.index) + #{`replacement`[`result[0]`].to_s} + self.slice(result.index + result[0].length);
826
1140
  }
827
- else if (block != null && block !== nil) {
828
- return self.replace(pattern, function() {
829
- // FIXME: this should be a formal MatchData object with all the goodies
830
- var match_data = []
831
- for (var i = 0, len = arguments.length; i < len; i++) {
832
- var arg = arguments[i];
833
- if (arg == undefined) {
834
- match_data.push(nil);
835
- }
836
- else {
837
- match_data.push(arg);
838
- }
839
- }
840
1141
 
841
- var str = match_data.pop();
842
- var offset = match_data.pop();
843
- var match_len = match_data.length;
1142
+ replacement = #{Opal.coerce_to(`replacement`, String, :to_str)};
844
1143
 
845
- // $1, $2, $3 not being parsed correctly in Ruby code
846
- for (var i = 1; i < match_len; i++) {
847
- Opal.gvars[String(i)] = match_data[i];
1144
+ replacement = replacement.replace(/([\\]+)([0-9+&`'])/g, function (original, slashes, command) {
1145
+ if (slashes.length % 2 === 0) {
1146
+ return original;
1147
+ }
1148
+ switch (command) {
1149
+ case "+":
1150
+ for (var i = result.length - 1; i > 0; i--) {
1151
+ if (result[i] !== undefined) {
1152
+ return slashes.slice(1) + result[i];
1153
+ }
848
1154
  }
849
- #{$& = `match_data[0]`};
850
- #{$~ = `match_data`};
851
- return block(match_data[0]);
852
- });
853
- }
854
- else {
855
- #{raise ArgumentError, 'wrong number of arguments (1 for 2)'}
856
- }
1155
+ return '';
1156
+ case "&": return slashes.slice(1) + result[0];
1157
+ case "`": return slashes.slice(1) + self.slice(0, result.index);
1158
+ case "'": return slashes.slice(1) + self.slice(result.index + result[0].length);
1159
+ default: return slashes.slice(1) + (result[command] || '');
1160
+ }
1161
+ }).replace(/\\\\/g, '\\');
1162
+
1163
+ return self.slice(0, result.index) + replacement + self.slice(result.index + result[0].length);
857
1164
  }
858
1165
  end
859
1166
 
@@ -864,13 +1171,21 @@ class String
864
1171
 
865
1172
  def sum(n = 16)
866
1173
  %x{
867
- var result = 0;
1174
+ n = #{Opal.coerce_to(`n`, Integer, :to_int)};
868
1175
 
869
- for (var i = 0, length = self.length; i < length; i++) {
870
- result += (self.charCodeAt(i) % ((1 << n) - 1));
1176
+ var result = 0,
1177
+ length = self.length,
1178
+ i = 0;
1179
+
1180
+ for (; i < length; i++) {
1181
+ result += self.charCodeAt(i);
871
1182
  }
872
1183
 
873
- return result;
1184
+ if (n <= 0) {
1185
+ return result;
1186
+ }
1187
+
1188
+ return result & (Math.pow(2, n) - 1);
874
1189
  }
875
1190
  end
876
1191
 
@@ -909,13 +1224,60 @@ class String
909
1224
 
910
1225
  def to_i(base = 10)
911
1226
  %x{
912
- var result = parseInt(self, base);
1227
+ var result,
1228
+ string = self.toLowerCase(),
1229
+ radix = #{Opal.coerce_to(`base`, Integer, :to_int)};
1230
+
1231
+ if (radix === 1 || radix < 0 || radix > 36) {
1232
+ #{raise ArgumentError, "invalid radix #{`radix`}"}
1233
+ }
913
1234
 
914
- if (isNaN(result)) {
1235
+ if (/^\s*_/.test(string)) {
915
1236
  return 0;
916
1237
  }
917
1238
 
918
- return result;
1239
+ string = string.replace(/^(\s*[+-]?)(0[bodx]?)(.+)$/, function (original, head, flag, tail) {
1240
+ switch (tail.charAt(0)) {
1241
+ case '+':
1242
+ case '-':
1243
+ return original;
1244
+ case '0':
1245
+ if (tail.charAt(1) === 'x' && flag === '0x' && (radix === 0 || radix === 16)) {
1246
+ return original;
1247
+ }
1248
+ }
1249
+ switch (flag) {
1250
+ case '0b':
1251
+ if (radix === 0 || radix === 2) {
1252
+ radix = 2;
1253
+ return head + tail;
1254
+ }
1255
+ break;
1256
+ case '0':
1257
+ case '0o':
1258
+ if (radix === 0 || radix === 8) {
1259
+ radix = 8;
1260
+ return head + tail;
1261
+ }
1262
+ break;
1263
+ case '0d':
1264
+ if (radix === 0 || radix === 10) {
1265
+ radix = 10;
1266
+ return head + tail;
1267
+ }
1268
+ break;
1269
+ case '0x':
1270
+ if (radix === 0 || radix === 16) {
1271
+ radix = 16;
1272
+ return head + tail;
1273
+ }
1274
+ break;
1275
+ }
1276
+ return original
1277
+ });
1278
+
1279
+ result = parseInt(string.replace(/_(?!_)/g, ''), radix);
1280
+ return isNaN(result) ? 0 : result;
919
1281
  }
920
1282
  end
921
1283
 
@@ -939,6 +1301,8 @@ class String
939
1301
  alias to_sym intern
940
1302
 
941
1303
  def tr(from, to)
1304
+ from = Opal.coerce_to(from, String, :to_str).to_s
1305
+ to = Opal.coerce_to(to, String, :to_str).to_s
942
1306
  %x{
943
1307
  if (from.length == 0 || from === to) {
944
1308
  return self;
@@ -952,7 +1316,7 @@ class String
952
1316
 
953
1317
  var inverse = false;
954
1318
  var global_sub = null;
955
- if (from_chars[0] === '^') {
1319
+ if (from_chars[0] === '^' && from_chars.length > 1) {
956
1320
  inverse = true;
957
1321
  from_chars.shift();
958
1322
  global_sub = to_chars[to_length - 1]
@@ -981,9 +1345,12 @@ class String
981
1345
  }
982
1346
  }
983
1347
  else if (in_range) {
984
- var start = last_from.charCodeAt(0) + 1;
1348
+ var start = last_from.charCodeAt(0);
985
1349
  var end = ch.charCodeAt(0);
986
- for (var c = start; c < end; c++) {
1350
+ if (start > end) {
1351
+ #{raise ArgumentError, "invalid range \"#{`String.fromCharCode(start)`}-#{`String.fromCharCode(end)`}\" in string transliteration"}
1352
+ }
1353
+ for (var c = start + 1; c < end; c++) {
987
1354
  from_chars_expanded.push(String.fromCharCode(c));
988
1355
  }
989
1356
  from_chars_expanded.push(ch);
@@ -1027,9 +1394,12 @@ class String
1027
1394
  }
1028
1395
  }
1029
1396
  else if (in_range) {
1030
- var start = last_from.charCodeAt(0) + 1;
1397
+ var start = last_from.charCodeAt(0);
1031
1398
  var end = ch.charCodeAt(0);
1032
- for (var c = start; c < end; c++) {
1399
+ if (start > end) {
1400
+ #{raise ArgumentError, "invalid range \"#{`String.fromCharCode(start)`}-#{`String.fromCharCode(end)`}\" in string transliteration"}
1401
+ }
1402
+ for (var c = start + 1; c < end; c++) {
1033
1403
  to_chars_expanded.push(String.fromCharCode(c));
1034
1404
  }
1035
1405
  to_chars_expanded.push(ch);
@@ -1076,6 +1446,8 @@ class String
1076
1446
  alias tr! <<
1077
1447
 
1078
1448
  def tr_s(from, to)
1449
+ from = Opal.coerce_to(from, String, :to_str).to_s
1450
+ to = Opal.coerce_to(to, String, :to_str).to_s
1079
1451
  %x{
1080
1452
  if (from.length == 0) {
1081
1453
  return self;
@@ -1089,7 +1461,7 @@ class String
1089
1461
 
1090
1462
  var inverse = false;
1091
1463
  var global_sub = null;
1092
- if (from_chars[0] === '^') {
1464
+ if (from_chars[0] === '^' && from_chars.length > 1) {
1093
1465
  inverse = true;
1094
1466
  from_chars.shift();
1095
1467
  global_sub = to_chars[to_length - 1]
@@ -1118,9 +1490,12 @@ class String
1118
1490
  }
1119
1491
  }
1120
1492
  else if (in_range) {
1121
- var start = last_from.charCodeAt(0) + 1;
1493
+ var start = last_from.charCodeAt(0);
1122
1494
  var end = ch.charCodeAt(0);
1123
- for (var c = start; c < end; c++) {
1495
+ if (start > end) {
1496
+ #{raise ArgumentError, "invalid range \"#{`String.fromCharCode(start)`}-#{`String.fromCharCode(end)`}\" in string transliteration"}
1497
+ }
1498
+ for (var c = start + 1; c < end; c++) {
1124
1499
  from_chars_expanded.push(String.fromCharCode(c));
1125
1500
  }
1126
1501
  from_chars_expanded.push(ch);
@@ -1164,9 +1539,12 @@ class String
1164
1539
  }
1165
1540
  }
1166
1541
  else if (in_range) {
1167
- var start = last_from.charCodeAt(0) + 1;
1542
+ var start = last_from.charCodeAt(0);
1168
1543
  var end = ch.charCodeAt(0);
1169
- for (var c = start; c < end; c++) {
1544
+ if (start > end) {
1545
+ #{raise ArgumentError, "invalid range \"#{`String.fromCharCode(start)`}-#{`String.fromCharCode(end)`}\" in string transliteration"}
1546
+ }
1547
+ for (var c = start + 1; c < end; c++) {
1170
1548
  to_chars_expanded.push(String.fromCharCode(c));
1171
1549
  }
1172
1550
  to_chars_expanded.push(ch);
@@ -1243,6 +1621,139 @@ class String
1243
1621
  def frozen?
1244
1622
  true
1245
1623
  end
1624
+
1625
+ def upto(stop, excl = false, &block)
1626
+ return enum_for :upto, stop, excl unless block_given?
1627
+ stop = Opal.coerce_to(stop, String, :to_str)
1628
+ %x{
1629
+ var a, b, s = self.toString();
1630
+
1631
+ if (s.length === 1 && stop.length === 1) {
1632
+
1633
+ a = s.charCodeAt(0);
1634
+ b = stop.charCodeAt(0);
1635
+
1636
+ while (a <= b) {
1637
+ if (excl && a === b) {
1638
+ break;
1639
+ }
1640
+ block(String.fromCharCode(a));
1641
+ a += 1;
1642
+ }
1643
+
1644
+ } else if (parseInt(s).toString() === s && parseInt(stop).toString() === stop) {
1645
+
1646
+ a = parseInt(s);
1647
+ b = parseInt(stop);
1648
+
1649
+ while (a <= b) {
1650
+ if (excl && a === b) {
1651
+ break;
1652
+ }
1653
+ block(a.toString());
1654
+ a += 1;
1655
+ }
1656
+
1657
+ } else {
1658
+
1659
+ while (s.length <= stop.length && s <= stop) {
1660
+ if (excl && s === stop) {
1661
+ break;
1662
+ }
1663
+ block(s);
1664
+ s = #{`s`.succ};
1665
+ }
1666
+
1667
+ }
1668
+ return self;
1669
+ }
1670
+ end
1671
+
1672
+ %x{
1673
+ function char_class_from_char_sets(sets) {
1674
+ function explode_sequences_in_character_set(set) {
1675
+ var result = '',
1676
+ i, len = set.length,
1677
+ curr_char,
1678
+ skip_next_dash,
1679
+ char_code_from,
1680
+ char_code_upto,
1681
+ char_code;
1682
+ for (i = 0; i < len; i++) {
1683
+ curr_char = set.charAt(i);
1684
+ if (curr_char === '-' && i > 0 && i < (len - 1) && !skip_next_dash) {
1685
+ char_code_from = set.charCodeAt(i - 1);
1686
+ char_code_upto = set.charCodeAt(i + 1);
1687
+ if (char_code_from > char_code_upto) {
1688
+ #{raise ArgumentError, "invalid range \"#{`char_code_from`}-#{`char_code_upto`}\" in string transliteration"}
1689
+ }
1690
+ for (char_code = char_code_from + 1; char_code < char_code_upto + 1; char_code++) {
1691
+ result += String.fromCharCode(char_code);
1692
+ }
1693
+ skip_next_dash = true;
1694
+ i++;
1695
+ } else {
1696
+ skip_next_dash = (curr_char === '\\');
1697
+ result += curr_char;
1698
+ }
1699
+ }
1700
+ return result;
1701
+ }
1702
+
1703
+ function intersection(setA, setB) {
1704
+ if (setA.length === 0) {
1705
+ return setB;
1706
+ }
1707
+ var result = '',
1708
+ i, len = setA.length,
1709
+ chr;
1710
+ for (i = 0; i < len; i++) {
1711
+ chr = setA.charAt(i);
1712
+ if (setB.indexOf(chr) !== -1) {
1713
+ result += chr;
1714
+ }
1715
+ }
1716
+ return result;
1717
+ }
1718
+
1719
+ var i, len, set, neg, chr, tmp,
1720
+ pos_intersection = '',
1721
+ neg_intersection = '';
1722
+
1723
+ for (i = 0, len = sets.length; i < len; i++) {
1724
+ set = #{Opal.coerce_to(`sets[i]`, String, :to_str)};
1725
+ neg = (set.charAt(0) === '^' && set.length > 1);
1726
+ set = explode_sequences_in_character_set(neg ? set.slice(1) : set);
1727
+ if (neg) {
1728
+ neg_intersection = intersection(neg_intersection, set);
1729
+ } else {
1730
+ pos_intersection = intersection(pos_intersection, set);
1731
+ }
1732
+ }
1733
+
1734
+ if (pos_intersection.length > 0 && neg_intersection.length > 0) {
1735
+ tmp = '';
1736
+ for (i = 0, len = pos_intersection.length; i < len; i++) {
1737
+ chr = pos_intersection.charAt(i);
1738
+ if (neg_intersection.indexOf(chr) === -1) {
1739
+ tmp += chr;
1740
+ }
1741
+ }
1742
+ pos_intersection = tmp;
1743
+ neg_intersection = '';
1744
+ }
1745
+
1746
+ if (pos_intersection.length > 0) {
1747
+ return '[' + #{Regexp.escape(`pos_intersection`)} + ']';
1748
+ }
1749
+
1750
+ if (neg_intersection.length > 0) {
1751
+ return '[^' + #{Regexp.escape(`neg_intersection`)} + ']';
1752
+ }
1753
+
1754
+ return null;
1755
+ }
1756
+ }
1246
1757
  end
1247
1758
 
1248
1759
  Symbol = String