ときどきの雑記帖 RE* (新南口)
デストロイ・オール・ヒューマンズ!
Moleskine 2022
ここ数年(というか来年で10年目)モレスキンの Weeklyを使っているのだけど、 あまり値段に見合った使い方をしていないので 来年(2022年)はどうしたもんかと思案する今日この頃。
「二つで充分ですよ」
とあるお店で。
自分の方が「二つで充分です」 と言ってしまったシチュエーションが発生。
「鶏肋」
先月発売の「丸」誌は特集が扶桑型戦艦だったのだけど、 最近某所で太平洋戦争中のその扶桑型を指して 「鶏肋」と表現していたのはうまかったなあ。などと。
gawk -v
前回 で終わりのつもりだったのだけど、 風呂入ってたときにふとここで呼び出している parse_escapeの実装が気になった (ので追いかけてみた)。
if (c == '\\') {
c = parse_escape(&pf);
if (c < 0) {
if (do_lint)
lintwarn(_("backslash string continuation is not portable"));
if ((flags & ELIDE_BACK_NL) != 0)
continue;
c = '\\';
}
*ptm++ = c;
} else
*ptm++ = c;
5.1.0でのparse_escapeで、ここのif文に関係する部分はこんな感じ。
case '\n':
return -2;
case 0:
(*string_ptr)--;
return -1;
}
\n
と\0
のどちらも負の数ではあるが明確に区別できる値を返している。
node.c のここではこの二つを(c < 0
という条件式によって)同一視しているけれども、
他の呼び出しではどうなのだろうかと見てみると
helpers/testdfa.c:54:int parse_escape(const char **string_ptr);
helpers/testdfa.c:430: c2 = parse_escape(&src);
helpers/testdfa.c:432: fprintf(stderr, "%s: parse_escape failed\n", __func__);
helpers/testdfa.c:484: * parse_escape:
helpers/testdfa.c:503:parse_escape(const char **string_ptr)
node.c:450: c = parse_escape(&pf);
node.c:522: * parse_escape:
node.c:541:parse_escape(const char **string_ptr)
re.c:145: c2 = parse_escape(&src);
vms/vms_args.c:254: c = parse_escape(&p);
そもそも呼び出しているのは三か所(testdfa.cのは同じ名前の別物と思われる)しかなく、 しかも vms/vms_args.cでは
#if 0 /* disable escape parsing; it's now done elsewhere within gawk */
register int c;
char *q = v_argv[v_argc] + (p - argv[i]);
do {
c = *p++;
if (c == '\\')
c = parse_escape(&p);
*q++ = (c >= 0 ? (char)c : '\\');
} while (*p != '\0');
*q = '\0';
#endif /*0*/
と無効にされていた。
もう一つの re.c の方も
/* We skip multibyte character, since it must not be a special
character. */
if ((gawk_mb_cur_max == 1 || ! is_multibyte) &&
(*src == '\\')) {
c = *++src;
switch (c) {
case '\0': /* \\ before \0, either dynamic data or real end of string */
if (src >= s + len)
*dest++ = '\\'; // at end of string, will fatal below
else
fatal(_("invalid NUL byte in dynamic regexp"));
break;
case 'a':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case 'x':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
c2 = parse_escape(&src);
if (c2 < 0)
cant_happen();
-2と-1の区別をしていなかった(c2 < 0
)。
しかもparse_escapeの呼び出しよりも前に自前で\0
のハンドリングをしているので、
parse_escapeを呼び出した先で\0
に遭遇することはない(はず)。
じゃあ、parse_escapeの方で何か変化がなかったかと調べてみると 2.11から(現状最新の5.1.0まで)は
case '\n':
return -2;
case 0:
(*string_ptr)--;
return -1;
なのだけど、2.10ではなんと awk.y - gawk.git - gawk
case '\n':
return -2;
case 0:
(*string_ptr)--;
return 0;
と case 0
のときの戻り値が異なっている。
とすると呼び出し元でc < 0
のように見たときの動作が変わってくる。
のだけど、定義はされているけど呼び出しているところが見当たらない。
awk.tab.c:2176:parse_escape(string_ptr)
awk.y:993:parse_escape(string_ptr)
どういうこと?
ついでに書くと 1.01 の時点 (awk.y - gawk.git - gawk) からparse_escapeはあるんだけど、ここでも定義だけで 呼び出している気配がない(正確には自分自身を呼び出している部分がある)。
過去のバージョンのいくつかについてnode.c(のmake_str_nodeで)の呼び出しを確認してみても
3.0
if ((flags & SCAN) != 0) { /* scan for escape sequences */
char *pf;
register char *ptm;
register int c;
register char *end;
end = &(r->stptr[len]);
for (pf = ptm = r->stptr; pf < end;) {
c = *pf++;
if (c == '\\') {
c = parse_escape(&pf);
if (c < 0) {
if (do_lint)
warning("backslash at end of string");
c = '\\';
}
*ptm++ = c;
} else
*ptm++ = c;
}
len = ptm - r->stptr;
erealloc(r->stptr, char *, len + 1, "make_str_node");
r->stptr[len] = '\0';
r->flags |= PERM;
}
2.15.6
if (flags & SCAN) { /* scan for escape sequences */
char *pf;
register char *ptm;
register int c;
register char *end;
end = &(r->stptr[len]);
for (pf = ptm = r->stptr; pf < end;) {
c = *pf++;
if (c == '\\') {
c = parse_escape(&pf);
if (c < 0) {
if (do_lint)
warning("backslash at end of string");
c = '\\';
}
*ptm++ = c;
} else
*ptm++ = c;
}
len = ptm - r->stptr;
erealloc(r->stptr, char *, len + 1, "make_str_node");
r->stptr[len] = '\0';
r->flags |= PERM;
}
2.11.1
if (scan) { /* scan for escape sequences */
for (pf = pt = r->stptr; pf < end;) {
c = *pf++;
if (c == '\\') {
c = parse_escape(&pf);
if (c < 0)
cant_happen();
*pt++ = c;
} else
*pt++ = c;
}
len = pt - r->stptr;
erealloc(r->stptr, char *, len + 1, "make_str_node");
r->stptr[len] = '\0';
r->flags |= PERM;
}
と、どうも-1と-2を区別して扱っていたことはないっぽい?
実はparse_escapeがその戻り値として0を返すことは他のケース
(\000という八進表記のデータが渡されたとき)もあって、
それと\0
を区別するために2.10→2.11のところで
-1を返すように変更したのかとも思うのだけど
(実は-vオプションが今のようになったのもここ)、
なにせ30年以上前のことだから調べようにもなあ。
glob zsh 14
どうもelse部の方が「本命」ぽいのでこちらを先に見る。
と書いたのだけど簡単にthen部の方にも触れておく。
zsh/glob.c at master · zsh-users/zsh
p = q->pat;
/* Now the actual matching for the current path section. */
if (p->flags & PAT_PURES) {
/*
* It's a straight string to the end of the path section.
*/
char *str = (char *)p + p->startoff;
int l = p->patmlen;
else部ではzreeddirを使った繰り返しがあるのにこちらにはなく、 PAT_PURESも
#define PAT_PURES 0x0020 /* Pattern is a pure string: set internally */
という定義なのでワイルドカードを含まないものの処理なのだろう。 ということでやっぱりスルー。
なんだけど
/* Do pattern matching on current path section. */
char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
int dirs = !!q->next;
DIR *lock = opendir(fn);
char *subdirs = NULL;
int subdirlen = 0;
if (lock == NULL)
return;
while ((fn = zreaddir(lock, 1)) && !errflag) {
/* prefix and suffix are zle trickery */
if (!dirs && !colonmod &&
((glob_pre && !strpfx(glob_pre, fn))
|| (glob_suf && !strsfx(glob_suf, fn))))
continue;
errsfound = errssofar;
if (pattry(p, fn)) {
/* if this name matches the pattern... */
if (pbcwdsav == pathbufcwd &&
strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
int err;
DPUTS(pathpos == pathbufcwd,
"BUG: filename longer than PATH_MAX");
err = lchdir(unmeta(pathbuf + pathbufcwd), &ds, 0);
if (err == -1)
break;
if (err) {
zerr("current directory lost during glob");
break;
}
pathbufcwd = pathpos;
}
if (dirs) {
省略
} else {
/* if the last filename component, just add it */
insert(fn, 1);
if (shortcircuit && shortcircuit == matchct) {
closedir(lock);
return;
}
}
}
}
closedir(lock);
この(とりあえず省略した) if (dirs)
のrhen部をどうしますかね。
matchctを操作するinsertを呼び出すのは、
ここ(if (dirs))のelse部と
if (p->flags & PAT_PURES) {
のthen部にある
もう一か所だけなので、
あまり深追いしないでよいかな。