shithub: mc

Download patch

ref: f8f5b71ca85fed63d295f50e3f45ba3230edaead
parent: a2a63e13a470401d10625a0d5be163767adb7e46
author: S. Gilles <sgilles@math.umd.edu>
date: Sat Nov 4 23:11:27 EST 2017

Implement graphemestep

And change 'strstep' to 'charstep' for consistency, now that it has
a sibling function.

--- a/lib/date/fmt.myr
+++ b/lib/date/fmt.myr
@@ -40,9 +40,9 @@
 const datefmt = {sb, fmt, d
 	var c
 	while fmt.len != 0
-		(c, fmt) = std.strstep(fmt)
+		(c, fmt) = std.charstep(fmt)
 		if c == '%'
-			(c, fmt) = std.strstep(fmt)
+			(c, fmt) = std.charstep(fmt)
 			match c
 			| 'a':	std.sbfmt(sb, "{}", _names.abbrevday[d.wday])
 			| 'A':	std.sbfmt(sb, "{}", _names.fullday[d.wday])
--- a/lib/date/parse.myr
+++ b/lib/date/parse.myr
@@ -70,9 +70,9 @@
 	z = ""
 	am = `std.None
 	while f.len != 0
-		(fc, f) = std.strstep(f)
+		(fc, f) = std.charstep(f)
 		if fc == '%'
-			(fc, f) = std.strstep(f)
+			(fc, f) = std.charstep(f)
 			if std.bshas(seen, fc)
 				err# = `std.Some `Doublefmt fc
 				-> s
@@ -120,7 +120,7 @@
 			| _:	std.fatal("unknown format character %c\n", fc)
 			;;
 		else
-			(sc, s) = std.strstep(s)
+			(sc, s) = std.charstep(s)
 			if std.isspace(fc) && std.isspace(sc)
 				s = eatspace(s)
 			elif sc != fc
@@ -157,7 +157,7 @@
 	var c
 
 	while std.isspace(std.decode(s))
-		(c, s) = std.strstep(s)
+		(c, s) = std.charstep(s)
 	;;
 	-> s
 }
@@ -246,7 +246,7 @@
 
 	num = s
 	for i = 0; i < min; i++
-		(c, s) = std.strstep(s)
+		(c, s) = std.charstep(s)
 		if !std.isdigit(c)
 			err# = `std.Some `Shortint
 			-> s
--- a/lib/http/parse.myr
+++ b/lib/http/parse.myr
@@ -258,7 +258,7 @@
 	s = ln#
 	ok = false
 	while true
-		(c, s) = std.strstep(s)
+		(c, s) = std.charstep(s)
 		dig = std.charval(c, base)
 		if dig >= 0 && dig < base
 			ok = true
--- a/lib/http/url.myr
+++ b/lib/http/url.myr
@@ -150,7 +150,7 @@
 	;;
 
 	match std.decode(url#)
-	| '?':	(_, url#) = std.strstep(url#)
+	| '?':	(_, url#) = std.charstep(url#)
 	| _:	-> `std.Err `Egarbled
 	;;
 
--- a/lib/std/cmp.myr
+++ b/lib/std/cmp.myr
@@ -60,8 +60,8 @@
 	var ca, cb
 
 	while a.len > 0 && b.len > 0
-		(ca, a) = std.strstep(a)
-		(cb, b) = std.strstep(b)
+		(ca, a) = std.charstep(a)
+		(cb, b) = std.charstep(b)
 		ca = toupper(ca)
 		cb = toupper(cb)
 		if ca < cb
--- a/lib/std/fmt.myr
+++ b/lib/std/fmt.myr
@@ -163,11 +163,11 @@
 	nparams = ap.tc.nelt
 	nfmt = 0
 	while fmt.len != 0
-		(c, fmt) = strstep(fmt)
+		(c, fmt) = charstep(fmt)
 		match c
 		| '{':
 			if decode(fmt) == '{'
-				(c, fmt) = strstep(fmt)
+				(c, fmt) = charstep(fmt)
 				sbputc(sb, '{')
 			else
 				(params, fmt) = getparams(fmt)
--- a/lib/std/hashfuncs.myr
+++ b/lib/std/hashfuncs.myr
@@ -50,8 +50,8 @@
 		if a.len == 0 || b.len == 0
 			break
 		;;
-		(ca, a) = std.strstep(a)
-		(cb, b) = std.strstep(b)
+		(ca, a) = std.charstep(a)
+		(cb, b) = std.charstep(b)
 		if std.tolower(ca) != std.tolower(cb)
 			-> false
 		;;
@@ -65,7 +65,7 @@
 
 	chars = [][:]
 	while s.len != 0
-		(c, s) = std.strstep(s)
+		(c, s) = std.charstep(s)
 		std.slpush(&chars, std.tolower(c))
 	;;
 	h = siphash24(slbytes(chars), Seed)
--- a/lib/std/optparse.myr
+++ b/lib/std/optparse.myr
@@ -105,7 +105,7 @@
 	var c
 	var arg
 
-	(c, ctx.curarg) = strstep(ctx.curarg)
+	(c, ctx.curarg) = charstep(ctx.curarg)
 
 	match optinfo(ctx, c)
 	| `None:
--- a/lib/std/striter.myr
+++ b/lib/std/striter.myr
@@ -33,7 +33,7 @@
 		if ci.rest.len == 0
 			-> false
 		;;
-		(c#, ci.rest) = strstep(ci.rest)
+		(c#, ci.rest) = charstep(ci.rest)
 		-> true
 	}
 
--- a/lib/std/test/utf.myr
+++ b/lib/std/test/utf.myr
@@ -28,4 +28,77 @@
 		"wrong width of runes")
 	std.assert(std.strcellwidth("𒀸 𒌋𒅗 𒆷 𒂅𒌒 𒍜 𒀭𒉌𒄿 𒈗 𒁁𒉌 𒋬") == 22, \
 		"wrong width of Cuneiform")
+
+	/* graphemestep() */
+	var s = "a史cЯx̀̀̀̀̀yz̉"
+	var sub, rest
+
+	(sub, rest) = std.graphemestep(s)
+	std.assert(std.streq(sub, "a"), "didn't get \"a\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "史"), "didn't get \"史\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "c"), "didn't get \"c\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "Я"), "didn't get \"Я\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "x̀̀̀̀̀"), "didn't get \"x̀̀̀̀̀\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "y"), "didn't get \"y\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "z̉"), "didn't get \"z̉\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(sub.len == 0, "didn't get \"\" as last grapheme")
+
+
+	/* with excessive combiners */
+	s = "c̸̶̡̡̗̣͕̪͖ͯ͑̈̄̿͊ͣ̈́͝ḧ̵̸̛̥͚̭̣͈͖̼͈͓͓̫͍́̓ͪͫ̋͘͡a̢̩̱̠̘̹̤̯͚̦̰̼̯̲̞͆͂̿ͬ̂͋͒̈ͅͅo̷̷̶̥͖̼̮̳̗͚ͦ̉̆̅̃̍ͤ̆͑ͣ̽́̚s̓̍̍̄͏̖̞̟̱́͡͡͝"
+
+	(sub, rest) = std.graphemestep(s)
+	std.assert(std.streq(sub, "c̸̶̡̡̗̣͕̪͖ͯ͑̈̄̿͊ͣ̈́͝"), "didn't get \"c̸̶̡̡̗̣͕̪͖ͯ͑̈̄̿͊ͣ̈́͝\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "ḧ̵̸̛̥͚̭̣͈͖̼͈͓͓̫͍́̓ͪͫ̋͘͡"), "didn't get \"ḧ̵̸̛̥͚̭̣͈͖̼͈͓͓̫͍́̓ͪͫ̋͘͡\" as next grapheme, it was {}", rest)
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "a̢̩̱̠̘̹̤̯͚̦̰̼̯̲̞͆͂̿ͬ̂͋͒̈ͅͅ"), "didn't get \"a̢̩̱̠̘̹̤̯͚̦̰̼̯̲̞͆͂̿ͬ̂͋͒̈ͅͅ\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "o̷̷̶̥͖̼̮̳̗͚ͦ̉̆̅̃̍ͤ̆͑ͣ̽́̚"), "didn't get \"o̷̷̶̥͖̼̮̳̗͚ͦ̉̆̅̃̍ͤ̆͑ͣ̽́̚\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "s̓̍̍̄͏̖̞̟̱́͡͡͝"), "didn't get \"s̓̍̍̄͏̖̞̟̱́͡͡͝\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(sub.len == 0, "didn't get \"\" as last grapheme")
+
+	/* now with invalid UTF-8 */
+	s = [ ('A' : byte), ('b' : byte), (0xFE : byte),
+	      (0xFF : byte), (0x92 : byte), ('c' : byte) ][:]
+
+	(sub, rest) = std.graphemestep(s)
+	std.assert(std.streq(sub, "A"), "didn't get \"A\" as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "b"), "didn't get \"b\" as next grapheme")
+
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, [ (0xFE : byte) ][:]), "didn't get 0xEE, len={} as next grapheme", sub.len)
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, [ (0xFF : byte) ][:]), "didn't get 0xEA as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, [ (0x92 : byte) ][:]), "didn't get 0xEF as next grapheme")
+
+	(sub, rest) = std.graphemestep(rest)
+	std.assert(std.streq(sub, "c"), "didn't get \"c\" as next grapheme")
 }
--- a/lib/std/utf.myr
+++ b/lib/std/utf.myr
@@ -12,7 +12,8 @@
 	const charlen	: (chr : char -> size)
 	const encode	: (buf : byte[:], chr : char -> size)
 	const decode	: (buf : byte[:] -> char)
-	const strstep	: (str : byte[:] -> (char, byte[:]))
+	const charstep	: (str : byte[:] -> (char, byte[:]))
+	const graphemestep : (str : byte[:] -> (byte[:], byte[:]))
 
 	const strcellwidth : (str : byte[:] -> size)
 ;;
@@ -59,11 +60,35 @@
 	var c
 	var b
 
-	(c, b) = strstep(buf)
+	(c, b) = charstep(buf)
 	-> c
 }
 
-const strstep = {str
+const graphemestep = {str
+	var len = 0
+	var rest = str
+	var c
+	var cn
+	var width
+
+	while rest.len > 0
+		(c, rest) = charstep(rest)
+		cn = cellwidth(c)
+
+		if (cn > 0 || c == Badchar) && width > 0
+			-> (str[:len], str[len:])
+		elif c == Badchar
+			-> (str[:1], str[1:])
+		else
+			len += charlen(c)
+			width += cn
+		;;
+	;;
+
+	-> (str[:len], str[len:])
+}
+
+const charstep = {str
 	var len
 	var mask
 	var chr
@@ -111,7 +136,7 @@
 	var n : size = 0
 
 	while s.len > 0
-		(c, s) = strstep(s)
+		(c, s) = charstep(s)
 		if c == Badchar
 			/* Something will probably be printed as U+FFFD */
 			n++
--- a/mbld/parse.myr
+++ b/mbld/parse.myr
@@ -732,8 +732,8 @@
 const matchc = {p, c
 	var chr, s
 
-	/* safe to use at eof: strstep returns (-1, "") */
-	(chr, s) = std.strstep(p.rest)
+	/* safe to use at eof: charstep returns (-1, "") */
+	(chr, s) = std.charstep(p.rest)
 	if c == chr
 		p.rest = s
 		-> true
@@ -749,7 +749,7 @@
 const getc = {p
 	var c, s
 
-	(c, s) = std.strstep(p.rest)
+	(c, s) = std.charstep(p.rest)
 	p.rest = s
 	-> c
 }