class String include Comparable `def._isString = true` `var native_string = "".constructor;` def self.try_convert(what) what.to_str rescue nil end def self.new(str = '') %x{ return new native_string(str) } end def %(data) if data.is_a?(Array) format(self, *data) else format(self, data) end end def *(count) %x{ if (count < 1) { return ''; } var result = '', pattern = #{self}.valueOf(); while (count > 0) { if (count & 1) { result += pattern; } count >>= 1, pattern += pattern; } return result; } end def +(other) `#{self}.toString() + other` end def <=>(other) %x{ if (typeof other !== 'string') { return nil; } return #{self} > other ? 1 : (#{self} < other ? -1 : 0); } end def <(other) `#{self} < other` end def <=(other) `#{self} <= other` end def >(other) `#{self} > other` end def >=(other) `#{self} >= other` end def ==(other) `other == native_string(#{self})` end alias === == def =~(other) %x{ if (typeof other === 'string') { #{ raise 'string given' }; } return #{other =~ self}; } end def [](index, length = undefined) %x{ var size = #{self}.length; if (index._isRange) { var exclude = index.exclude, length = index.end, index = index.begin; if (index < 0) { index += size; } if (length < 0) { length += size; } if (!exclude) { length += 1; } if (index > size) { return nil; } length = length - index; if (length < 0) { length = 0; } return #{self}.substr(index, length); } if (index < 0) { index += #{self}.length; } if (length == null) { if (index >= #{self}.length || index < 0) { return nil; } return #{self}.substr(index, 1); } if (index > #{self}.length || index < 0) { return nil; } return #{self}.substr(index, length); } end def capitalize `#{self}.charAt(0).toUpperCase() + #{self}.substr(1).toLowerCase()` end def casecmp(other) %x{ if (typeof other !== 'string') { return other; } var a = #{self}.toLowerCase(), b = other.toLowerCase(); return a > b ? 1 : (a < b ? -1 : 0); } end def center(width, padstr = ' ') %x{ if (width <= #{self}.length) { return #{self}; } else { var ljustified = #{self.ljust( ((width + self.size)/2).floor, padstr)}; var rjustified = #{self.rjust( ((width + self.size)/2).ceil, padstr)}; return ljustified + rjustified.slice(#{self}.length); } } end def chars %x{ for (var i = 0, length = #{self}.length; i < length; i++) { #{yield `#{self}.charAt(i)`} } } end def chomp(separator = $/) %x{ var strlen = #{self}.length; var seplen = separator.length; if (strlen > 0) { if (separator === "\\n") { var last = #{self}.charAt(strlen - 1); if (last === "\\n" || last == "\\r") { var result = #{self}.substr(0, strlen - 1); if (strlen > 1 && #{self}.charAt(strlen - 2) === "\\r") { result = #{self}.substr(0, strlen - 2); } return result; } } else if (separator === "") { return #{self}.replace(/(?:\\n|\\r\\n)+$/, ''); } else if (strlen >= seplen) { var tail = #{self}.substr(-1 * seplen); if (tail === separator) { return #{self}.substr(0, strlen - seplen); } } } return #{self} } end def chop `#{self}.substr(0, #{self}.length - 1)` end def chr `#{self}.charAt(0)` end def clone `#{self}.slice()` end def count(str) `(#{self}.length - #{self}.replace(new RegExp(str,"g"), '').length) / str.length` end alias dup clone def downcase `#{self}.toLowerCase()` end alias each_char chars def each_line (separator = $/) return self.split(separator).each unless block_given? %x{ var chomped = #{self.chomp}; var trailing_separator = #{self}.length != chomped.length var splitted = chomped.split(separator); if (!#{block_given?}) { result = [] for (var i = 0, length = splitted.length; i < length; i++) { if (i < length - 1 || trailing_separator) { result.push(splitted[i] + separator); } else { result.push(splitted[i]); } } return #{`result`.each}; } for (var i = 0, length = splitted.length; i < length; i++) { if (i < length - 1 || trailing_separator) { #{yield `splitted[i] + separator`} } else { #{yield `splitted[i]`} } } } end def empty? `#{self}.length === 0` end def end_with?(*suffixes) %x{ for (var i = 0, length = suffixes.length; i < length; i++) { var suffix = suffixes[i]; if (#{self}.length >= suffix.length && #{self}.substr(0 - suffix.length) === suffix) { return true; } } return false; } end alias eql? == def equal?(val) `#{self}.toString() === val.toString()` end def getbyte(idx) `#{self}.charCodeAt(idx)` end def gsub(pattern, replace = undefined, &block) if pattern.is_a?(String) pattern = /#{Regexp.escape(pattern)}/ end %x{ var pattern = pattern.toString(), options = pattern.substr(pattern.lastIndexOf('/') + 1) + 'g', regexp = pattern.substr(1, pattern.lastIndexOf('/') - 1); #{self}.$sub._p = block; return #{self}.$sub(new RegExp(regexp, options), replace); } end def hash `#{self}.toString()` end def hex to_i 16 end def include?(other) `#{self}.indexOf(other) !== -1` end def index(what, offset = nil) %x{ if ( !(what != null && (what._isString || what._isRegexp)) ) { #{raise TypeError, 'type mismatch'}; } var result = -1; if (offset != null) { if (offset < 0) { offset = offset + #{self}.length; } if (offset > #{self}.length) { return nil; } if (#{what.is_a?(Regexp)}) { result = #{what =~ `#{self}.substr(offset)` || -1} } else { result = #{self}.substr(offset).indexOf(#{what}); } if (result !== -1) { result += offset; } } else { if (#{what.is_a?(Regexp)}) { result = #{(what =~ self) || -1} } else { result = #{self}.indexOf(#{what}); } } return result === -1 ? nil : result; } end def inspect %x{ var escapable = /[\\\\\\"\\x00-\\x1f\\x7f-\\x9f\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g, meta = { '\\b': '\\\\b', '\\t': '\\\\t', '\\n': '\\\\n', '\\f': '\\\\f', '\\r': '\\\\r', '"' : '\\\\"', '\\\\': '\\\\\\\\' }; escapable.lastIndex = 0; return escapable.test(#{self}) ? '"' + #{self}.replace(escapable, function(a) { var c = meta[a]; return typeof c === 'string' ? c : '\\\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + #{self} + '"'; } end def intern self end alias lines each_line def length `#{self}.length` end def ljust(width, padstr = ' ') %x{ var length = #{self}.length; if (width <= length) { return #{self}; } else { var index = -1, result = ""; while (++index < (width - length)) { result += padstr; } return #{self} + result.slice(0, width - length); } } end def lstrip `#{self}.replace(/^\\s*/, '')` end def match(pattern, pos = undefined, &block) (pattern.is_a?(Regexp) ? pattern : /#{Regexp.escape(pattern)}/).match(self, pos, &block) end def next %x{ if (#{self}.length === 0) { return ""; } var initial = #{self}.substr(0, #{self}.length - 1); var last = native_string.fromCharCode(#{self}.charCodeAt(#{self}.length - 1) + 1); return initial + last; } end def ord `#{self}.charCodeAt(0)` end def partition(str) %x{ var result = #{self}.split(str); var splitter = (result[0].length === #{self}.length ? "" : str); return [result[0], splitter, result.slice(1).join(str.toString())]; } end def reverse `#{self}.split('').reverse().join('')` end # TODO handle case where search is regexp def rindex(search, offset = undefined) %x{ var search_type = (search == null ? Opal.NilClass : search.constructor); if (search_type != native_string && search_type != RegExp) { var msg = "type mismatch: " + search_type + " given"; #{raise TypeError.new(`msg`)}; } if (#{self}.length == 0) { return search.length == 0 ? 0 : nil; } var result = -1; if (offset != null) { if (offset < 0) { offset = #{self}.length + offset; } if (search_type == native_string) { result = #{self}.lastIndexOf(search, offset); } else { result = #{self}.substr(0, offset + 1).$reverse().search(search); if (result !== -1) { result = offset - result; } } } else { if (search_type == native_string) { result = #{self}.lastIndexOf(search); } else { result = #{self}.$reverse().search(search); if (result !== -1) { result = #{self}.length - 1 - result; } } } return result === -1 ? nil : result; } end def rjust(width, padstr = ' ') %x{ if (width <= #{self}.length) { return #{self}; } else { var n_chars = Math.floor(width - #{self}.length) var n_patterns = Math.floor(n_chars/padstr.length); var result = Array(n_patterns + 1).join(padstr); var remaining = n_chars - result.length; return result + padstr.slice(0, remaining) + #{self}; } } end def rstrip `#{self}.replace(/\\s*$/, '')` end def scan(pattern, &block) %x{ if (pattern.global) { // should we clear it afterwards too? pattern.lastIndex = 0; } else { // rewrite regular expression to add the global flag to capture pre/post match pattern = new RegExp(pattern.source, 'g' + (pattern.multiline ? 'm' : '') + (pattern.ignoreCase ? 'i' : '')); } var result = []; var match; while ((match = pattern.exec(#{self})) != null) { var match_data = #{MatchData.new `pattern`, `match`}; if (block === nil) { match.length == 1 ? result.push(match[0]) : result.push(match.slice(1)); } else { match.length == 1 ? block(match[0]) : block.apply(#{self}, match.slice(1)); } } return (block !== nil ? #{self} : result); } end alias size length alias slice [] def split(pattern = $; || ' ', limit = undefined) `#{self}.split(pattern, limit)` end def start_with?(*prefixes) %x{ for (var i = 0, length = prefixes.length; i < length; i++) { if (#{self}.indexOf(prefixes[i]) === 0) { return true; } } return false; } end def strip `#{self}.replace(/^\\s*/, '').replace(/\\s*$/, '')` end def sub(pattern, replace = undefined, &block) %x{ if (typeof(replace) === 'string') { // convert Ruby back reference to JavaScript back reference replace = replace.replace(/\\\\([1-9])/g, '$$$1') return #{self}.replace(pattern, replace); } if (block !== nil) { return #{self}.replace(pattern, function() { // FIXME: this should be a formal MatchData object with all the goodies var match_data = [] for (var i = 0, len = arguments.length; i < len; i++) { var arg = arguments[i]; if (arg == undefined) { match_data.push(nil); } else { match_data.push(arg); } } var str = match_data.pop(); var offset = match_data.pop(); var match_len = match_data.length; // $1, $2, $3 not being parsed correctly in Ruby code //for (var i = 1; i < match_len; i++) { // __gvars[String(i)] = match_data[i]; //} #{$& = `match_data[0]`}; #{$~ = `match_data`}; return block(match_data[0]); }); } else if (replace !== undefined) { if (#{replace.is_a?(Hash)}) { return #{self}.replace(pattern, function(str) { var value = #{replace[str]}; return (value == null) ? nil : #{value.to_s}; }); } else { replace = #{String.try_convert(replace)}; if (replace == null) { #{raise TypeError, "can't convert #{replace.class} into String"}; } return #{self}.replace(pattern, replace); } } else { // convert Ruby back reference to JavaScript back reference replace = replace.toString().replace(/\\\\([1-9])/g, '$$$1') return #{self}.replace(pattern, replace); } } end alias succ next def sum(n = 16) %x{ var result = 0; for (var i = 0, length = #{self}.length; i < length; i++) { result += (#{self}.charCodeAt(i) % ((1 << n) - 1)); } return result; } end def swapcase %x{ var str = #{self}.replace(/([a-z]+)|([A-Z]+)/g, function($0,$1,$2) { return $1 ? $0.toUpperCase() : $0.toLowerCase(); }); if (#{self}.constructor === native_string) { return str; } return #{self.class.new `str`}; } end def to_a %x{ if (#{self}.length === 0) { return []; } return [#{self}]; } end def to_f %x{ var result = parseFloat(#{self}); return isNaN(result) ? 0 : result; } end def to_i(base = 10) %x{ var result = parseInt(#{self}, base); if (isNaN(result)) { return 0; } return result; } end def to_proc %x{ var name = '$' + #{self}; return function(arg) { var meth = arg[name]; return meth ? meth.call(arg) : arg.$method_missing(name); }; } end def to_s `#{self}.toString()` end alias to_str to_s alias to_sym intern def to_n `#{self}.valueOf()` end def tr(from, to) %x{ if (from.length == 0 || from === to) { return #{self}; } var subs = {}; var from_chars = from.split(''); var from_length = from_chars.length; var to_chars = to.split(''); var to_length = to_chars.length; var inverse = false; var global_sub = null; if (from_chars[0] === '^') { inverse = true; from_chars.shift(); global_sub = to_chars[to_length - 1] from_length -= 1; } var from_chars_expanded = []; var last_from = null; var in_range = false; for (var i = 0; i < from_length; i++) { var char = from_chars[i]; if (last_from == null) { last_from = char; from_chars_expanded.push(char); } else if (char === '-') { if (last_from === '-') { from_chars_expanded.push('-'); from_chars_expanded.push('-'); } else if (i == from_length - 1) { from_chars_expanded.push('-'); } else { in_range = true; } } else if (in_range) { var start = last_from.charCodeAt(0) + 1; var end = char.charCodeAt(0); for (var c = start; c < end; c++) { from_chars_expanded.push(native_string.fromCharCode(c)); } from_chars_expanded.push(char); in_range = null; last_from = null; } else { from_chars_expanded.push(char); } } from_chars = from_chars_expanded; from_length = from_chars.length; if (inverse) { for (var i = 0; i < from_length; i++) { subs[from_chars[i]] = true; } } else { if (to_length > 0) { var to_chars_expanded = []; var last_to = null; var in_range = false; for (var i = 0; i < to_length; i++) { var char = to_chars[i]; if (last_from == null) { last_from = char; to_chars_expanded.push(char); } else if (char === '-') { if (last_to === '-') { to_chars_expanded.push('-'); to_chars_expanded.push('-'); } else if (i == to_length - 1) { to_chars_expanded.push('-'); } else { in_range = true; } } else if (in_range) { var start = last_from.charCodeAt(0) + 1; var end = char.charCodeAt(0); for (var c = start; c < end; c++) { to_chars_expanded.push(native_string.fromCharCode(c)); } to_chars_expanded.push(char); in_range = null; last_from = null; } else { to_chars_expanded.push(char); } } to_chars = to_chars_expanded; to_length = to_chars.length; } var length_diff = from_length - to_length; if (length_diff > 0) { var pad_char = (to_length > 0 ? to_chars[to_length - 1] : ''); for (var i = 0; i < length_diff; i++) { to_chars.push(pad_char); } } for (var i = 0; i < from_length; i++) { subs[from_chars[i]] = to_chars[i]; } } var new_str = '' for (var i = 0, length = #{self}.length; i < length; i++) { var char = #{self}.charAt(i); var sub = subs[char]; if (inverse) { new_str += (sub == null ? global_sub : char); } else { new_str += (sub != null ? sub : char); } } return new_str; } end def tr_s(from, to) %x{ if (from.length == 0) { return #{self}; } var subs = {}; var from_chars = from.split(''); var from_length = from_chars.length; var to_chars = to.split(''); var to_length = to_chars.length; var inverse = false; var global_sub = null; if (from_chars[0] === '^') { inverse = true; from_chars.shift(); global_sub = to_chars[to_length - 1] from_length -= 1; } var from_chars_expanded = []; var last_from = null; var in_range = false; for (var i = 0; i < from_length; i++) { var char = from_chars[i]; if (last_from == null) { last_from = char; from_chars_expanded.push(char); } else if (char === '-') { if (last_from === '-') { from_chars_expanded.push('-'); from_chars_expanded.push('-'); } else if (i == from_length - 1) { from_chars_expanded.push('-'); } else { in_range = true; } } else if (in_range) { var start = last_from.charCodeAt(0) + 1; var end = char.charCodeAt(0); for (var c = start; c < end; c++) { from_chars_expanded.push(native_string.fromCharCode(c)); } from_chars_expanded.push(char); in_range = null; last_from = null; } else { from_chars_expanded.push(char); } } from_chars = from_chars_expanded; from_length = from_chars.length; if (inverse) { for (var i = 0; i < from_length; i++) { subs[from_chars[i]] = true; } } else { if (to_length > 0) { var to_chars_expanded = []; var last_to = null; var in_range = false; for (var i = 0; i < to_length; i++) { var char = to_chars[i]; if (last_from == null) { last_from = char; to_chars_expanded.push(char); } else if (char === '-') { if (last_to === '-') { to_chars_expanded.push('-'); to_chars_expanded.push('-'); } else if (i == to_length - 1) { to_chars_expanded.push('-'); } else { in_range = true; } } else if (in_range) { var start = last_from.charCodeAt(0) + 1; var end = char.charCodeAt(0); for (var c = start; c < end; c++) { to_chars_expanded.push(native_string.fromCharCode(c)); } to_chars_expanded.push(char); in_range = null; last_from = null; } else { to_chars_expanded.push(char); } } to_chars = to_chars_expanded; to_length = to_chars.length; } var length_diff = from_length - to_length; if (length_diff > 0) { var pad_char = (to_length > 0 ? to_chars[to_length - 1] : ''); for (var i = 0; i < length_diff; i++) { to_chars.push(pad_char); } } for (var i = 0; i < from_length; i++) { subs[from_chars[i]] = to_chars[i]; } } var new_str = '' var last_substitute = null for (var i = 0, length = #{self}.length; i < length; i++) { var char = #{self}.charAt(i); var sub = subs[char] if (inverse) { if (sub == null) { if (last_substitute == null) { new_str += global_sub; last_substitute = true; } } else { new_str += char; last_substitute = null; } } else { if (sub != null) { if (last_substitute == null || last_substitute !== sub) { new_str += sub; last_substitute = sub; } } else { new_str += char; last_substitute = null; } } } return new_str; } end def upcase `#{self}.toUpperCase()` end def freeze self end def frozen? true end end Symbol = String class MatchData < Array attr_reader :post_match, :pre_match, :regexp, :string def self.new(regexp, match_groups) %x{ var instance = new Opal.MatchData._alloc; for (var i = 0, len = match_groups.length; i < len; i++) { var group = match_groups[i]; if (group == undefined) { instance.push(nil); } else { instance.push(group); } } instance._begin = match_groups.index; instance.regexp = regexp; instance.string = match_groups.input; instance.pre_match = #{$` = `instance.string.substr(0, regexp.lastIndex - instance[0].length)`}; instance.post_match = #{$' = `instance.string.substr(regexp.lastIndex)`}; return #{$~ = `instance`}; } end def begin(pos) %x{ if (pos == 0 || pos == 1) { return #{self}._begin; } else { #{raise ArgumentError, 'MatchData#begin only supports 0th element'}; } } end def captures `#{self}.slice(1)` end def inspect %x{ var str = "<#MatchData " + #{self}[0].$inspect() for (var i = 1, len = #{self}.length; i < len; i++) { str += " " + i + ":" + #{self}[i].$inspect(); } str += ">"; return str; } end def to_s `#{self}[0]` end def to_n `#{self}.valueOf()` end def values_at(*indexes) %x{ var vals = []; var match_length = #{self}.length; for (var i = 0, length = indexes.length; i < length; i++) { var pos = indexes[i]; if (pos >= 0) { vals.push(#{self}[pos]); } else { pos = match_length + pos; if (pos > 0) { vals.push(#{self}[pos]); } else { vals.push(nil); } } } return vals; } end end