code: plan9front

Download patch

ref: 05889d4454c61fb6f1caf6aba300ce23388ec47b
parent: 006b925a2aedeff506430c4b028a04687e8560ff
author: Jacob Moody <moody@posixcafe.org>
date: Mon Jan 30 23:24:12 EST 2023

ktrans: tests and various bug fixes

* exit if we get eof on kbdtap
* do not nuke the line if we restore a kanji selection without okurigana
* guard against unfortunate scheduling, the dictthread needs to get
through all it can before the keythread processes more. In typical use,
the processing was fast enough to never notice this condition but writing
out a large set of input can trigger it.

--- a/sys/src/cmd/ktrans/main.c
+++ b/sys/src/cmd/ktrans/main.c
@@ -408,6 +408,8 @@
 	return e - b;
 }
 
+static int compacting = 0;
+
 static void
 dictthread(void*)
 {
@@ -437,6 +439,7 @@
 
 	threadsetname("dict");
 	while(recv(dictch, m) != -1){
+		compacting = 1;
 		for(p = m+1; *p; p += n){
 			n = chartorune(&r, p);
 			switch(r){
@@ -478,7 +481,8 @@
 				}
 				if(kouho[selected] == nil){
 					/* cycled through all matches; bail */
-					emitutf(output, backspace, utflen(okuri.b));
+					if(utflen(okuri.b) != 0)
+						emitutf(output, backspace, utflen(okuri.b));
 					emitutf(output, backspace, utflen(last.b));
 					emitutf(output, line.b, 0);
 					emitutf(output, okuri.b, 0);
@@ -547,6 +551,7 @@
 			hmapget(dict, line.b, kouho);
 			send(displaych, kouho);
 		}
+		compacting = 0;
 	}
 }
 
@@ -623,6 +628,8 @@
 		}
 
 		for(p = m+1; *p; p += n){
+			while(compacting)
+				yield();
 			n = chartorune(&r, p);
 			if(checklang(&lang, r)){
 				emitutf(dictch, "", 1);
@@ -718,6 +725,7 @@
 				return;
 		}
 	}
+	threadexitsall(nil);
 }
 
 static void
--- a/sys/src/cmd/ktrans/mkfile
+++ b/sys/src/cmd/ktrans/mkfile
@@ -8,3 +8,9 @@
 	main.$O\
 
 </sys/src/cmd/mkone
+
+$O.test: test.$O
+	$LD $LDFLAGS -o $target $prereq
+
+test:V: $O.test $O.out
+	$O.test $O.out
--- /dev/null
+++ b/sys/src/cmd/ktrans/test.c
@@ -1,0 +1,111 @@
+#include <u.h>
+#include <libc.h>
+
+struct {
+	char *input;
+	Rune *expect;
+} set[] = {
+	"n", L"ん",
+	"no", L"の",
+	"nno", L"んの",
+	"neko", L"猫",
+	"neko", L"ねこ",
+	"watashi", L"私",
+	"tanoShi", L"楽し",
+	"oreNO", L"俺の",
+
+	"watashiHAmainichi35funijouaruIte,saraNI10fundenshaNInoTtegakkouNIkayoImasu.\nkenkouNOijiNImoyakuDAtteimasuga,nakanakatanoshiImonodesu.\n",
+	L"私は毎日35分以上歩いて、更に10分電車に乗って学校に通います。\n健康の維持にも役だっていますが、なかなかたのしいものです。\n",
+};
+
+char*
+makemsg(char *s)
+{
+	char *out, *d;
+	int i, n;
+
+	n = strlen(s) + 1;
+	out = mallocz(n * 3, 1);
+	for(d = out, i = 0; i < n; i++){
+		*d++ = 'c';
+		if(i == n - 1)
+			*d++ = 1;
+		else
+			*d++ = s[i];
+		*d++ = '\0';
+	}
+	return out;
+}
+
+void
+main(int argc, char **argv)
+{
+	int io1[2], io2[2];
+	int i;
+	int n;
+	char *p, *e;
+	static char buf[256];
+	Rune r;
+	char *to;
+	char *bin;
+	static Rune stack[256];
+	static int nstack;
+
+	if(argc < 2)
+		sysfatal("usage: %s binary", argv[0]);
+
+	bin = argv[1];
+	pipe(io1);
+	pipe(io2);
+	if(fork() == 0){
+		dup(io1[0], 0);
+		dup(io2[0], 1);
+		close(io1[1]); close(io2[1]);
+		execl(bin, "ktrans", "-l", "jp", "-G", nil);
+		sysfatal("exec: %r");
+	}
+	close(io1[0]); close(io2[0]);
+	for(i = 0; i < nelem(set); i++){
+		nstack = 0;
+		stack[nstack] = 0;
+		to = makemsg(set[i].input);
+		for(;;){
+			write(io1[1], to, strlen(to) + 1);
+			if(to[1] == 1)
+					break;
+			to += strlen(to)+1;
+		}
+		for(;;) {
+			n = read(io2[1], buf, sizeof buf);
+			if(n <= 0)
+				break;
+			e = buf + n;
+			for(p = buf; p < e; p += (strlen(p)+1)){
+				assert(*p == 'c');
+				chartorune(&r, p+1);
+				switch(r){
+				case 1:
+					goto Verify;
+				case 8:
+					if(nstack == 0)
+						sysfatal("buffer underrun");
+					nstack--;
+					stack[nstack] = 0;
+					break;
+				default:
+					stack[nstack++] = r;
+					stack[nstack] = 0;
+					break;
+				}
+			}
+		}
+	Verify:
+		if(runestrcmp(set[i].expect, stack) != 0){
+			fprint(2, "%S != %S\n", stack, set[i].expect);
+			exits("fail");
+		}
+	}
+	close(io1[1]); close(io2[1]);
+	waitpid();
+	exits(nil);
+}