1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
/*
Copyright (C) 2026 Avi Halachmi (:avih) avihpit@yahoo.com
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see https://www.gnu.org/licenses/ .
*/
#include <ctype.h>
/* don't include libbb.h which redefines isalpha etc - we use the platform's */
#include "actype.h"
/* isblank is c99 and not always available. just provide our own. */
#define isblank ac_isblank
static int ac_isblank(int c)
{
return c == ' ' || c == '\t';
}
int isactype(int c, actype_t t)
{
return t ? isactype_not0(c, t) : 0;
}
/* perfect hash table from the 3-chars prefix of string s to the exact
* single potential class name to test (using strcmp etc).
* the hash function H(...) can use all 3 chars, but currently it only
* uses s[1] and s[2]. It was chosen to work mod32. it might be possible
* to find a simple function which works mod16, and cut idx size to 16.
* (#define HSIZE 16 and play with H - clang warns on collisions).
*
* this hash is only guaranteed for ASCII a-x values. see below for non-ascii.
*/
#define HSIZE 32 /* power of 2 hash buckets */
#define H(s0,s1,s2) (((unsigned)(s1) - (unsigned)(s2)) & (HSIZE - 1))
static const char idx[HSIZE] = {
[H('a','l','n')] = 0, [H('a','l','p')] = 1, [H('b','l','a')] = 2,
[H('c','n','t')] = 3, [H('d','i','g')] = 4, [H('g','r','a')] = 5,
[H('l','o','w')] = 6, [H('p','r','i')] = 7, [H('p','u','n')] = 8,
[H('s','p','a')] = 9, [H('u','p','p')] = 10, [H('x','d','i')] = 11
};
/* the hash uses s[2] to distinguish alnum/alpha, but can't read past '\0' */
#define POTENTIAL_CLASS_IDX(s) (s[0] && s[1] ? idx[H(s[0], s[1], s[2])] : 0)
/* with enum, isactype_not0 access this table - can't be static */
#if !ACTYPE_ENUM
static
#endif
const _isactype_t _actype_fns[] = {
0,
isalnum, isalpha, isblank, iscntrl, isdigit, isgraph,
islower, isprint, ispunct, isspace, isupper, isxdigit
};
#if ACTYPE_ENUM
#define ACTYPE_FROM_IDX(i) (1 + (i))
#else
#define ACTYPE_FROM_IDX(i) ((uintptr_t)_actype_fns[1 + (i)])
#endif
#define CHAR_CLASSES \
"alnum\0alpha\0blank\0cntrl\0digit\0graph\0" \
"lower\0print\0punct\0space\0upper\0xdigit"
/* c[0]-c[4] are not 0, so we can skip these 0-tests, and do that inline.
* we could drop-in libbb is_prefixed_with, but actail would be x2 slower.
*/
#define IS_PREFIXED_WITH_CLASS(s, c) \
(s[0]==c[0] && s[1]==c[1] && s[2]==c[2] && s[3]==c[3] && s[4]==c[4] \
? c[5] ? /* xdigit */ s[5] == 't' ? s+6 : 0 \
: s+5 \
: 0)
actype_t actail(const char *s, int *len)
{
int i = POTENTIAL_CLASS_IDX(s);
const char *c = CHAR_CLASSES + i * 6;
s = IS_PREFIXED_WITH_CLASS(s, c);
if (!s)
return 0;
/* done: s is just past the matched name, verify tail based on len */
if (len) {
if (*s++ != ':' || *s != ']')
return 0;
*len = 7 + (i == 11); /* xdigit is 6, others are 5 */
} else if (*s) {
return 0;
}
return ACTYPE_FROM_IDX(i);
}
/* for arbitrary monotonic (up) charsets, we could use non-prefect "hash" of
* simply the s[0] value, so that a... and p... need one or two comparisons:
* #define HSIZE ('x'-'a'+1) // (last-first+1)
* #define H(s0) ((unsigned)(s0) % HSIZE)
* and the values "1" and "8" (alpha, punct) are removed as collisions:
* idx[HSIZE] = { [H('a')]=0, [H('b')]=2..., [H('p')]=7, [H('s')]=9,... }
* and the code should loop on i as long as s[0] == c[0], which is
* 1 or 2 iterations for a... and p..., and 0 or 1 iteration otherwise.
* such solution also doesn't need the initial s[0] and s[1] test:
* for (i=idx[H(*s)], c=...+i*6; *s==*c; ++i, c+=6)
* if ((s=is_prefixed_with(s+1,c+1))) { <found - finalize> }
* return 0;
*/
|