/* String method tests... */
scope{
assert 'ab' === 'a'.concat('b');
assert 'ABC' === 'a'.concat('b','c').toUpper();
assert 42 === '0*'.byteAt(1);
assert undefined === '0'.byteAt(3);
assert '*' === '1*1'.charAt(1);
assert '☺' === '1☺1'.charAt(1);
assert '*' === '1*1'[1];
assert '☺' === '1☺'[1];
assert '☺' === '☺1'[0];
assert undefined === '☺1'[2];
assert '.'.isAscii();
assert !'1☺1'.isAscii();
assert '...'[0].isAscii();
assert '1☺1'[0].isAscii();
assert !'1☺1'[1].isAscii();
var x = "hi! there! this string cannot not get interned (not simple/short enough)";
var y = "".concat(x);
assert x === y
/* String.concat() optimization: "".concat(oneString) returns
oneString. Unfortunately, from the script level, we can't
_really_ be sure that we have the same string instance. Except
possibly via this little cheat... */;
assert typeinfo(refcount x) === typeinfo(refcount y);
var z = [x,x,x]; // just grab a few refs to x
assert typeinfo(refcount x) === typeinfo(refcount z.0);
assert typeinfo(refcount x) === typeinfo(refcount y);
y = "".concat("",y) /* bypasses optimization */;
assert typeinfo(refcount x) === typeinfo(refcount z.0);
assert typeinfo(refcount x) > typeinfo(refcount y);
// WEIRD... the refcount on y is 1 higher on the first hit:
// print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
// it's almost like an argv isn't letting its ref go (but gets
// swept up by the next call), but i just checked and argv is
// ref/unref'd properly. Hmmm. Aha: it's the pending result value
// in the stack (s2_eval_ptoker(), actually) at that point! Correct behaviour -
// it gets cleaned up by s2_eval_ptoker() after the next expression (the first
// call, were the refcount is +1 higher than we might (otherwise) expect).
//print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
//print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
}
scope {
assert 'Z☺Z' === 'z☺z'.toUpper();
assert 'z☺z' === 'Z☺Z'.toLower();
var str = '↻Æ©';
assert 5 === "©123©".length();
assert 5 === "©123©".#;
assert 7 === "©123©".lengthBytes();
assert 3 === str.length();
assert 3 === str.#;
assert 7 === str.lengthBytes();
assert '↻' === str.charAt(0);
assert 'Æ' === str.charAt(1);
assert '©' === str.charAt(2);
assert '©' === str[2];
assert undefined === str.charAt(3);
assert undefined === str[3];
assert 'Æ©' === str.substr(1);
assert '↻Æ' === str.substr(0,2);
str = 'こんにちは' /*no idea what this says (or not) - copied it from the net.
Google says it means "Hi there"*/;
assert 15 === str.lengthBytes();
assert 5 === str.length();
assert 'こ' === str.charAt(0);
assert 'ん' === str.charAt(1);
assert 'に' === str.charAt(2);
assert 'ち' === str.charAt(3);
assert 'は' === str.charAt(4);
assert 'んにち' === str.substr(1,3);
assert 'にちは' === str.substr(2,-1);
assert '' === str.substr(0,0);
assert 'CWAL_RC_RANGE' === catch {"abc"[-1]}.codeString()
/* negative (from-the-end) indexes are a potential TODO, currently disallowed. */;
assert 'c' === 'abc'.charAt(2).charAt(0).charAt(0).charAt(0);
assert 'c' === 'abc'[2][0][0]
/* SOMETIMES a cwal-level assertion: lifetime issue. Triggering it
depends on recycling settings, engine state, and... lemme
guess... right: string interning again >:(. Somewhere we're
missing a reference we need. This was "resolved" in
s2_process_top() by adding its newly-pushed result value to the
eval-holder list (if such a list is active).
*/;
assert 'c' === 'abc'[2][0][0][0][0][0][0][0][0][0];
};
scope {
/* The subtleties of split()... */
const split = proc(s,sep){
sep || (sep = ':');
var ar = s.split(sep);
var j = ar.join(sep);
assert j === s;
return ar;
};
scope {
var str = "//aaa//b//c//xy//";
var sep = '//';
var ar = split(str,sep);
assert ar.join(sep) === str;
assert "" == ar.0;
assert "" === ar.5;
assert 6 === ar.length();
assert 'aaa' === ar.1;
assert 'xy' === ar.4;
}
scope {
var str = "aaa/b/c/xy/";
var sep = '/';
var ar = split(str, sep);
assert ar.join(sep) === str;
assert 5 === ar.length();
assert 'aaa' === ar.0;
assert "" === ar.4;
}
scope {
var str = "aaa©b©c©";
var sep = '©';
var ar = split(str, sep);
assert ar.join(sep) === str;
assert 4 === ar.length();
assert 'aaa' === ar.0;
assert "" === ar.3;
}
scope{
var str = ":::";
var sep = ':';
var ar = split(str, ':');
assert ar.join(sep) === str;
assert 4 === ar.length();
assert !ar.0;
assert !ar.3;
}
scope {
var str = ":";
var sep = str;
var ar = split(str, sep);
assert ar.join(sep) === str;
assert 2 === ar.length();
assert !ar.0;
assert !ar.1;
}
scope {
var ar = split(":a:b:",':');
assert 4 === ar.length();
assert !ar.0;
assert 'a' === ar.1;
assert 'b' === ar.2;
assert !ar.3;
}
scope {
var ar = split("abc", '|');
assert 1 === ar.length();
assert 'abc' === ar.0;
}
scope {
var ar = split("", '.');
assert 1 === ar.length();
assert "" === ar.0;
}
scope {
var ar = split("a:::b", ':');
assert 4 === ar.length();
assert "a" === ar.0;
assert !ar.1;
assert !ar.2;
assert "b" === ar.3;
}
scope {
// The 'limit' semantics changed on 20160213 to match what JS does.
// Note that we still don't accept split() with no args, like JS does.
var ar = "a:b:c".split(':',2);
assert 2 === ar.length();
assert 'b' === ar.1;
ar = 'a:b:c'.split('/');
assert 1 === ar.length();
assert 'a:b:c' === ar.0;
}
scope {
/* "Empty split". String interning saves us scads of memory here
when the input string is large... */
var ar = "abc".split('');
assert typeinfo(isarray ar);
assert 3 === ar.length();
assert 'c' === ar.2;
ar = "abc".split('',2);
assert 2 === ar.length();
assert 'b' === ar.1;
}
} /* end split() */
scope {
//assert "3.00.1" === "3.00.1" /* just making sure */;
//print("3.00.1", "3.0"+0.1);
assert "3.00.1" === "3.0"+0.1; // string on the left
assert 3.7 === 0.7 + "3"; // string on the right
assert 3.74 === 0.7 + "3" + "0.04";
assert 3.1 === 3.1 + "abc"; // "abc"==0
assert 5 === +"5";
assert -5 === -"5";
assert -5 === +"-5";
assert 1.2 === -"-1.2"; // but beware of precision changes on such conversions!
}
scope {
const sp = "".prototype;
sp.firstChar = proc(asInteger=false){
return this.charAt(0, asInteger);
};
assert "a" === "abc".firstChar();
//unset "".prototype.firstChar; // hmmm - unset cannot deal with this.
unset sp.firstChar;
}
scope {
var a = "a";
a += "b";
assert 'ab' === a;
}
scope {
/* String.replace() tests... */
proc x(haystack,needle,replace/*,limitAndOrExpect*/){
const expect = argv.4 ||| argv.3,
limit = argv.4 ? argv.3 : 0;
const v = haystack.replace(needle, replace, limit);
(expect === v)
&& (assert 1 /*String.replace() check passed*/)
&& return x;
throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]";
}
("abc", 'b', 'B', 'aBc')
('abcabc', 'b', 'BB', 1, 'aBBcabc')
('ab©ab©ab©', 'b', 'BB', 2, 'aBB©aBB©ab©')
('(C)ab(C)', '(C)', '©', '©ab©')
('abc', 'x', 'y', 'abc')
('abc', 'abc', '', '')
('a\r\nc\r\n', '\r\n', '\n', 'a\nc\n')
;;
}
scope {
assert 2 === 'abcd'.indexOf('c');
assert 0 > 'abcd'.indexOf('e');
assert 2 === 'ab©'.indexOf('©');
assert 3 === 'ab©c'.indexOf('c');
assert 3 === 'ab©cはd'.indexOf('cはd');
// 20190713 bugfix: the indexOf() of same-length strings
// was just plain broken due to an "optimization" which
// backfired.
assert 'a'.indexOf('b') < 0;
assert 'cab'.indexOf('cab') === 0;
}
scope {
/* 20191220: an invalid \Uxxxxxxxx value now triggers a syntax
error rather than being immediately fatal with no error
location info. Because it's a syntax error, and not an exception,
it can only be catch'd if it comes from inside/through a block.
i.e. (catch '\U00E0A080') will not convert it to an exception, but
the following will... */
const ex = catch {
'\U00E0A080'
};
assert ex;
assert ex.message.indexOf('Uxx')>0;
assert 'CWAL_SCR_SYNTAX' === ex.codeString();
}