Files
j2s/src/lib/libs/string.ts
TopchetoEU 74f08b7483
Some checks failed
tagged-release / Tagged Release (push) Failing after 7m8s
small stdlib improvements
2024-12-28 13:17:45 +02:00

277 lines
8.9 KiB
TypeScript

import { TypeError } from "./errors.ts";
import { func, number, regex, string } from "./primordials.ts";
import { RegExp } from "./regex.ts";
import { applyReplaces, applySplits, limitI, type ReplaceRange, symbols, unwrapThis, valueKey, wrapI } from "./utils.ts";
const trimStartRegex = new regex("^\\s+", false, false, false, false, false);
const trimEndRegex = new regex("\\s+$", false, false, false, false, false);
export const String = (() => {
class String {
[valueKey]!: string;
public at(index: number) {
throw "Not implemented :/";
return unwrapThis(this, "string", String, "String.prototype.at")[index];
}
public toString() {
return unwrapThis(this, "string", String, "String.prototype.toString");
}
public valueOf() {
return unwrapThis(this, "string", String, "String.prototype.valueOf");
}
public includes(search: string, offset = 0) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
return string.indexOf(self, (String as any)(search), +offset, false) >= 0;
}
public startsWith(search: string) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
if (self.length < search.length) return false;
return string.substring(self, 0, search.length) === search;
}
public endsWith(search: string) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
if (self.length < search.length) return false;
return string.substring(self, self.length - search.length, self.length) === search;
}
public indexOf(search: string, offset = 0) {
const self = unwrapThis(this, "string", String, "String.prototype.indexOf");
offset = +offset;
return string.indexOf(self, search, offset, false);
}
public lastIndexOf(search: string, offset = 0) {
const self = unwrapThis(this, "string", String, "String.prototype.lastIndexOf");
offset = +offset;
return string.indexOf(self, search, offset, true);
}
public trim() {
const self = unwrapThis(this, "string", String, "String.prototype.trim");
const start = trimStartRegex.exec(self, 0, false);
const end = trimEndRegex.exec(self, 0, false);
const startI = start == null ? 0 : start.end;
const endI = end == null ? self.length : end.matches.index!;
return string.substring(self, startI, endI);
}
public trimStart() {
const self = unwrapThis(this, "string", String, "String.prototype.trim");
const start = trimStartRegex.exec(self, 0, false);
const startI = start == null ? 0 : start.end;
return string.substring(self, startI, self.length);
}
public trimEnd() {
const self = unwrapThis(this, "string", String, "String.prototype.trim");
const end = trimEndRegex.exec(self, 0, false);
const endI = end == null ? self.length : end.matches.index!;
return string.substring(self, 0, endI);
}
public trimLeft() {
return func.invoke(String.prototype.trimStart, this, []);
}
public trimRight() {
return func.invoke(String.prototype.trimEnd, this, []);
}
public charAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charAt");
return self[i];
}
public charCodeAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charCodeAt");
return self[i] ? string.toCharCode(self[i]) : number.NaN;
}
public codePointAt(i: number) {
const self = unwrapThis(this, "string", String, "String.prototype.charCodeAt");
return i >= 0 && i < self.length ? string.toCodePoint(self, i) : number.NaN;
}
public split(val?: any, limit?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.split");
if (val === undefined) return [self];
if (val !== null && typeof val === "object" && symbols.split in val) {
return val[symbols.split](self, limit);
}
val = (String as any)(val);
return applySplits(self, limit, offset => {
const start = string.indexOf(self, val, offset, false);
if (start < 0) return undefined;
else return { start, end: start + val.length };
});
}
public replace(val: any, replacer: any) {
const self = unwrapThis(this, "string", String, "String.prototype.replace");
if (val !== null && typeof val === "object" && symbols.replace in val) {
return val[symbols.replace](self, replacer);
}
else val = (String as any)(val);
const i = string.indexOf(self, val, 0);
return applyReplaces(self, [{ start: i, end: i + val.length, matches: [val] }], replacer, false);
}
public replaceAll(val: any, replacer: any) {
const self = unwrapThis(this, "string", String, "String.prototype.replaceAll");
if (val !== null && typeof val === "object" && symbols.replace in val) {
if (val instanceof RegExp && !val.global) throw new TypeError("replaceAll must be called with a global RegExp");
return val[symbols.replace](self, replacer);
}
else val = (String as any)(val);
let offset = 0;
const matches: ReplaceRange[] = [];
const add = val.length === 0 ? 1 : val.length;
while (true) {
const i = string.indexOf(self, val, offset);
if (i < 0) break;
matches[matches.length] = { start: i, end: i + val.length, matches: [val] };
if (val.length === 0)
offset = i + add;
}
return applyReplaces(self, matches, replacer, false);
}
public repeat(n: number) {
const self = unwrapThis(this, "string", String, "String.prototype.replaceAll");
const res: string[] = [];
for (let i = 0; i < n; i++) {
res[i] = self;
}
return string.stringBuild(res);
}
public slice(start = 0, end?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.slice");
if (end === undefined) end = self.length;
start = limitI(wrapI(start, self.length), self.length);
end = limitI(wrapI(end, self.length), self.length);
if (end <= start) return "";
return string.substring(self, start, end);
}
public substring(start = 0, end?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.substring");
if (end === undefined) end = self.length;
start = limitI(start, self.length);
end = limitI(end, self.length);
if (end <= start) return "";
return string.substring(self, start, end);
}
public substr(start = 0, count?: number) {
const self = unwrapThis(this, "string", String, "String.prototype.substr");
count = self.length - start;
start = limitI(start, self.length);
count = limitI(count, self.length - start);
if (count <= 0) return "";
return string.substring(self, start, count + start);
}
public concat() {
const self = unwrapThis(this, "string", String, "String.prototype.concat");
const parts = [self];
for (let i = 0; i < arguments.length; i++) {
parts[i + 1] = (String as any)(arguments[i]);
}
return string.stringBuild(parts);
}
public toLowerCase() {
const self = unwrapThis(this, "string", String, "String.prototype.toLowerCase");
return string.lower(self);
}
public toUpperCase() {
const self = unwrapThis(this, "string", String, "String.prototype.toLowerCase");
return string.upper(self);
}
public match(regex: RegExp) {
const self = unwrapThis(this, "string", String, "String.prototype.match");
if (!(regex instanceof RegExp)) throw new TypeError("Regexp expected for String.prototype.match");
if (regex.global) {
let matches: string[] | null = null;
while (true) {
const match = regex.exec(self);
if (match == null) break;
matches ||= [];
matches[matches.length] = match[0];
}
return matches;
}
else return regex.exec(self);
}
public [symbols.iterator]() {
var i = 0;
var arr: string | undefined = unwrapThis(this, "string", String, "String.prototype[Symbol.iterator]");
return {
next () {
if (arr == null) return { done: true, value: undefined };
if (i > arr.length) {
arr = undefined;
return { done: true, value: undefined };
}
else {
var val = arr[i++];
if (i >= arr.length) arr = undefined;
return { done: false, value: val };
}
},
[symbols.iterator]() { return this; }
};
}
public constructor (value?: unknown) {
if (func.invokeType(arguments, this) === "call") {
if (arguments.length === 0) return "" as any;
else if (typeof value === "symbol") return value.toString() as any;
else return (value as any) + "" as any;
}
this[valueKey] = (String as any)(value);
}
public static fromCharCode() {
const res: string[] = [];
res[arguments.length] = "";
for (let i = 0; i < arguments.length; i++) {
res[i] = string.fromCharCode(+arguments[i]);
}
return string.stringBuild(res);
}
public static fromCodePoint() {
const res: string[] = [];
res[arguments.length] = "";
for (var i = 0; i < arguments.length; i++) {
res[i] = string.fromCodePoint(+arguments[i]);
}
return string.stringBuild(res);
}
}
func.setCallable(String, true);
func.setConstructable(String, true);
return String as any as typeof String & ((value?: unknown) => string);
})();
export type String = InstanceType<typeof String>;