τau / strings /

strings.tau

source
/Users/niconex/Documents/tau/stdlib/strings/strings.tau
1# strings - operations on text.2#3# A string is a run of bytes and everything here counts bytes, the way Go does4# it: Index gives a byte offset, and slicing at one that falls inside a letter5# outside ASCII cuts the letter in half. The utf8 module is where the code6# points are.7#8#	strings = import("strings")9#10#	strings.Split("a,b,c", ",")        # [a, b, c]11#	strings.Cut("key=value", "=")      # {Before: key, After: value, Found: true}12#	strings.TrimSpace("  hi\n")        # hi13#14# Nothing is modified in place, since a string cannot be: every function here15# returns a new one.1617buffer = import("buffer")1819spaces = "\t\n\v\f\r "2021Contains = fn(str, sub) {22	maxAttempts = len(str) - len(sub)23	for i = 0; i <= maxAttempts; ++i {24		if sub == slice(str, i, i + len(sub)) {25			return true26		}27	}2829	return false30}3132ContainsAny = fn(str, chars) {33	for i = 0; i < len(chars); ++i {34		if Contains(str, chars[i]) {35			return true36		}37	}3839	return false40}4142Count = fn(str, sub) {43	total = 04445	maxAttempts = len(str) - len(sub)46	for i = 0; i <= maxAttempts; ++i {47		if sub == slice(str, i, i + len(sub)) {48			++total49		}50	}5152	return total53}5455cutResult = fn(before, after, found) {56	res = new()57	res.Before = before58	res.After = after59	res.Found = found6061	return res62}6364Cut = fn(str, sub) {65	maxAttempts = len(str) - len(sub)66	for i = 0; i <= maxAttempts; ++i {67		if sub == slice(str, i, i + len(sub)) {68			return cutResult(69				slice(str, 0, i),70				slice(str, i + len(sub), len(str)),71				true72			)73		}74	}7576	return cutResult(str, "", false)77}7879HasPrefix = fn(str, pre) {80	if len(pre) > len(str) {81		return false82	}8384	return slice(str, 0, len(pre)) == pre85}8687HasSuffix = fn(str, sub) {88	if len(sub) > len(str) {89		return false90	}9192	return slice(str, len(str) - len(sub), len(str)) == sub93}9495Index = fn(str, sub) {96	maxAttempts = len(str) - len(sub)97	for i = 0; i <= maxAttempts; ++i {98		if sub == slice(str, i, i + len(sub)) {99			return i100		}101	}102103	return -1104}105106IndexAny = fn(str, chars) {107	index = -1108	for i = 0; i < len(chars); ++i {109		tmp = Index(str, chars[i])110		if tmp != -1 && index > tmp || index == -1 {111			index = tmp112		}113	}114115	return index116}117118Join = fn(arr, sep) {119	b = buffer.Builder()120121	for i = 0; i < len(arr); ++i {122		if type(arr[i]) != "string" {123			return error("array element at index {i} is not a string")124		}125126		if i > 0 {127			b.Write(sep)128		}129		b.Write(arr[i])130	}131132	return b.String()133}134135LastIndex = fn(str, sub) {136	for i = len(str) - len(sub); i >= 0; --i {137		if sub == slice(str, i, i + len(sub)) {138			return i139		}140	}141142	return -1143}144145LastIndexAny = fn(str, chars) {146	index = -1147	for i = 0; i < len(chars); ++i {148		tmp = LastIndex(str, chars[i])149		if tmp > index {150			index = tmp151		}152	}153154	return index155}156157Repeat = fn(str, n) {158	b = buffer.Builder()159160	for i = 0; i < n; ++i {161		b.Write(str)162	}163164	return b.String()165}166167Reverse = fn(str) {168	b = buffer.Builder()169170	for i = len(str) - 1; i >= 0; --i {171		b.Write(str[i])172	}173174	return b.String()175}176177SplitAfterN = fn(str, sep, n) {178	if sep == "" {179		return error("splitAfter: empty separator")180	}181182	ret = []183	for (idx = Index(str, sep)) != -1 && n != 0 {184		ret = append(ret, slice(str, 0, idx + len(sep)))185		str = slice(str, idx + len(sep), len(str))186		--n187	}188189	return append(ret, str)190}191192SplitAfter = fn(str, sep) { SplitAfterN(str, sep, -1) }193194SplitN = fn(str, sep, n) {195	if sep == "" {196		return error("split: empty separator")197	}198199	# Empty pieces are kept, "a,,b" splits into three.200	ret = []201	for (idx = Index(str, sep)) != -1 && n != 0 {202		ret = append(ret, slice(str, 0, idx))203		str = slice(str, idx + len(sep), len(str))204		--n205	}206207	return append(ret, str)208}209210Split = fn(str, sep) { SplitN(str, sep, -1) }211212# Fields splits around runs of whitespace, dropping the empty pieces.213Fields = fn(str) {214	ret = []215	field = ""216217	for i = 0; i < len(str); ++i {218		if Contains(spaces, str[i]) {219			if len(field) > 0 {220				ret = append(ret, field)221				field = ""222			}223		} else {224			field = field + str[i]225		}226	}227228	if len(field) > 0 {229		ret = append(ret, field)230	}231	return ret232}233234TrimPrefix = fn(str, pre) {235	return if len(pre) > len(str) || pre != slice(str, 0, len(pre)) {236		str237	} else {238		slice(str, len(pre), len(str))239	}240}241242TrimSuffix = fn(str, sub) {243	return if len(sub) > len(str) || sub != slice(str, len(str) - len(sub), len(str)) {244		str245	} else {246		slice(str, 0, len(str) - len(sub))247	}248}249250ReplaceAll = fn(str, old, new) { Join(Split(str, old), new) }251252Replace = fn(str, old, new, n) {253	if n < 0 {254		return ReplaceAll(str, old, new)255	}256257	ret = ""258	for i = 0; i < n; ++i {259		if (idx = Index(str, old)) == -1 {260			break261		}262		ret += slice(str, 0, idx) + new263		str = slice(str, idx + len(old), len(str))264	}265266	return ret + str267}268269isalpha = fn(char) { char >= 97 && char <= 122 || char >= 65 && char <= 90 }270islower = fn(char) { char >= 97 && char <= 122 }271isupper = fn(char) { char >= 65 && char <= 90 }272toupper = fn(char) { char - 32 }273tolower = fn(char) { char + 32 }274275ToUpper = fn(str) {276	b = bytes(str)277	ret = []278279	for i = 0; i < len(b); ++i {280		char = b[i]281		ret = append(ret, if islower(char) { toupper(char) } else { char })282	}283284	return string(bytes(ret))285}286287ToLower = fn(str) {288	b = bytes(str)289	ret = []290291	for i = 0; i < len(b); ++i {292		char = b[i]293		ret = append(ret, if isupper(char) { tolower(char) } else { char })294	}295296	return string(bytes(ret))297}298299ToTitle = fn(str) {300	toks = Split(str, " ")301	ret = []302303	for i = 0; i < len(toks); ++i {304		b = bytes(toks[i])305		tmp = []306307		tmp = append(tmp, if islower(b[0]) { toupper(b[0]) } else { b[0] })308		for j = 1; j < len(b); ++j {309			tmp = append(tmp, b[j])310		}311312		ret = append(ret, string(bytes(tmp)))313	}314315	return Join(ret, " ")316}317318TrimLeft = fn(str, cutset) {319	for start = 0; start < len(str); ++start {320		if !Contains(cutset, str[start]) {321			break322		}323	}324325	return slice(str, start, len(str))326}327328TrimRight = fn(str, cutset) {329	for stop = len(str); stop > 0; --stop {330		if !Contains(cutset, str[stop - 1]) {331			break332		}333	}334335	return slice(str, 0, stop)336}337338# PadLeft returns str padded with pad until it is width long, or str itself339# if it is already that long. Pad defaults to a space.340PadLeft = fn(str, width, pad) {341	if pad == null || len(pad) == 0 {342		pad = " "343	}344345	b = buffer.Builder()346	for len(str) + b.Len() < width {347		b.Write(pad)348	}349	b.Write(str)350351	return b.String()352}353354# PadRight is PadLeft, with the padding on the other side.355PadRight = fn(str, width, pad) {356	if pad == null || len(pad) == 0 {357		pad = " "358	}359360	b = buffer.Builder()361	b.Write(str)362	for b.Len() < width {363		b.Write(pad)364	}365366	return b.String()367}368369Trim = fn(str, cutset) { TrimRight(TrimLeft(str, cutset), cutset) }370371TrimSpace = fn(str) { Trim(str, spaces) }