git: 9front

Download patch

ref: 5acdc23ae98067cafc55879e2636e990b1837fe1
parent: 782a15e482b1b315ea3bdf59ccd36fcc31645ca2
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);
+}
--