xref: /inferno-os/appl/cmd/wc.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Wc;
2
3#
4# wc -- count things in utf-encoded text files
5# Bugs:
6#	The only white space characters recognized are ' ', '\t' and '\n', even though
7#	ISO 10646 has many more blanks scattered through it.
8#	Should count characters that cannot occur in any rune (hex f0-ff) separately.
9#	Should count non-canonical runes (e.g. hex c1,80 instead of hex 40).
10#
11
12include "sys.m";
13	sys: Sys;
14
15include "draw.m";
16
17Wc: module
18{
19	init:	fn(ctxt: ref Draw->Context, args: list of string);
20};
21
22NBUF:	con 8*1024;
23
24stderr:	ref Sys->FD;
25nline, tnline, pline: int;
26nword, tnword, pword: int;
27nchar, tnchar, pchar: int;
28nbadr, tnbadr, pbadr: int;
29nbyte, tnbyte, pbyte: int;
30
31init(nil: ref Draw->Context, argv: list of string)
32{
33	sys = load Sys Sys->PATH;
34	stderr = sys->fildes(2);
35
36	for(argv = tl argv; argv != nil; argv = tl argv){
37		arg := hd argv;
38		if(len arg < 2 || arg[0] != '-' || arg[1] == '-')
39			break;
40		for(i := 1; i < len arg; i++){
41			case arg[i]{
42			'l' => pline++;
43			'w' => pword++;
44			'c' => pchar++;
45			'e' => pbadr++;
46			'b' => pbyte++;
47			* =>
48				sys->fprint(stderr, "usage: wc [-lwcbe] [file ...]\n");
49				raise "fail:usage";
50			}
51		}
52	}
53	if(pline+pword+pchar+pbadr+pbyte == 0)
54		pline = pword = pchar = 1;
55	argc := len argv;
56	if(argc == 0)
57		count(sys->fildes(0), "");
58	else{
59		for(; argv != nil; argv = tl argv){
60			name := hd argv;
61			f := sys->open(name, sys->OREAD);
62			if(f == nil)
63				sys->fprint(stderr, "wc: can't open %s: %r\n", name);
64			else{
65				count(f, name);
66				tnline += nline;
67				tnword += nword;
68				tnchar += nchar;
69				tnbadr += nbadr;
70				tnbyte += nbyte;
71				f = nil;
72			}
73		}
74		if(argc > 1)
75			report(tnline, tnword, tnchar, tnbadr, tnbyte, "total");
76	}
77	exit;
78}
79report(nline, nword, nchar, nbadr, nbyte: int, fname: string)
80{
81	line := "";
82	if(pline)
83		line += sys->sprint(" %7d", nline);
84	if(pword)
85		line += sys->sprint(" %7d", nword);
86	if(pchar)
87		line += sys->sprint(" %7d", nchar);
88	if(pbadr)
89		line += sys->sprint(" %7d", nbadr);
90	if(pbyte)
91		line += sys->sprint(" %7d", nbyte);
92	if(fname != nil)
93		line += sys->sprint(" %s", fname);
94	sys->print("%s\n", line[1:]);
95}
96#
97# How it works.  Start in statesp.  Each time we read a character,
98# increment various counts, and do state transitions according to the
99# following table.  If we're not in statesp or statewd when done, the
100# file ends with a partial rune.
101#        |                character
102#  state |09,20| 0a  |00-7f|80-bf|c0-df|e0-ef|f0-ff
103# -------+-----+-----+-----+-----+-----+-----+-----
104# statesp|ASP  |ASPN |AWDW |AWDWX|AC2W |AC3W |AWDWX
105# statewd|ASP  |ASPN |AWD  |AWDX |AC2  |AC3  |AWDX
106# statec2|ASPX |ASPNX|AWDX |AWDR |AC2X |AC3X |AWDX
107# statec3|ASPX |ASPNX|AWDX |AC2R |AC2X |AC3X |AWDX
108#
109			# actions
110	AC2,		# enter statec2
111	AC2R,		# enter statec2, don't count a rune
112	AC2W,		# enter statec2, count a word
113	AC2X,		# enter statec2, count a bad rune
114	AC3,		# enter statec3
115	AC3W,		# enter statec3, count a word
116	AC3X,		# enter statec3, count a bad rune
117	ASP,		# enter statesp
118	ASPN,		# enter statesp, count a newline
119	ASPNX,		# enter statesp, count a newline, count a bad rune
120	ASPX,		# enter statesp, count a bad rune
121	AWD,		# enter statewd
122	AWDR,		# enter statewd, don't count a rune
123	AWDW,		# enter statewd, count a word
124	AWDWX,		# enter statewd, count a word, count a bad rune
125	AWDX:		# enter statewd, count a bad rune
126		con byte iota;
127
128statesp := array[256] of{	# looking for the start of a word
129AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 00-07
130AWDW, ASP,  ASPN, AWDW, AWDW, AWDW, AWDW, AWDW,	# 08-0f
131AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 10-17
132AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 18-1f
133ASP,  AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 20-27
134AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 28-2f
135AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 30-37
136AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 38-3f
137AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 40-47
138AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 48-4f
139AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 50-57
140AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 58-5f
141AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 60-67
142AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 68-6f
143AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 70-77
144AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW,	# 78-7f
145AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 80-87
146AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 88-8f
147AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 90-97
148AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 98-9f
149AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a0-a7
150AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a8-af
151AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b0-b7
152AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b8-bf
153AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W,	# c0-c7
154AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W,	# c8-cf
155AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W,	# d0-d7
156AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W,	# d8-df
157AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W,	# e0-e7
158AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W,	# e8-ef
159AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f0-f7
160AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f8-ff
161};
162statewd := array[256] of {	# looking for the next character in a word
163AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 00-07
164AWD,  ASP,  ASPN, AWD,  AWD,  AWD,  AWD,  AWD,	# 08-0f
165AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 10-17
166AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 18-1f
167ASP,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 20-27
168AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 28-2f
169AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 30-37
170AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 38-3f
171AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 40-47
172AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 48-4f
173AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 50-57
174AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 58-5f
175AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 60-67
176AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 68-6f
177AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 70-77
178AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,  AWD,	# 78-7f
179AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 80-87
180AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 88-8f
181AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 90-97
182AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 98-9f
183AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# a0-a7
184AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# a8-af
185AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# b0-b7
186AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# b8-bf
187AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,	# c0-c7
188AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,	# c8-cf
189AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,	# d0-d7
190AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,  AC2,	# d8-df
191AC3,  AC3,  AC3,  AC3,  AC3,  AC3,  AC3,  AC3,	# e0-e7
192AC3,  AC3,  AC3,  AC3,  AC3,  AC3,  AC3,  AC3,	# e8-ef
193AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# f0-f7
194AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# f8-ff
195};
196statec2 := array[256] of {	# looking for 10xxxxxx to complete a rune
197AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 00-07
198AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX,	# 08-0f
199AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 10-17
200AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 18-1f
201ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 20-27
202AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 28-2f
203AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 30-37
204AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 38-3f
205AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 40-47
206AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 48-4f
207AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 50-57
208AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 58-5f
209AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 60-67
210AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 68-6f
211AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 70-77
212AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 78-7f
213AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# 80-87
214AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# 88-8f
215AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# 90-97
216AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# 98-9f
217AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# a0-a7
218AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# a8-af
219AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# b0-b7
220AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR,	# b8-bf
221AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# c0-c7
222AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# c8-cf
223AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# d0-d7
224AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# d8-df
225AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X,	# e0-e7
226AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X,	# e8-ef
227AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# f0-f7
228AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# f8-ff
229};
230statec3 := array[256] of {	# looking for 10xxxxxx,10xxxxxx to complete a rune
231AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 00-07
232AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX,	# 08-0f
233AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 10-17
234AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 18-1f
235ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 20-27
236AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 28-2f
237AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 30-37
238AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 38-3f
239AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 40-47
240AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 48-4f
241AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 50-57
242AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 58-5f
243AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 60-67
244AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 68-6f
245AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 70-77
246AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# 78-7f
247AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# 80-87
248AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# 88-8f
249AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# 90-97
250AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# 98-9f
251AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# a0-a7
252AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# a8-af
253AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# b0-b7
254AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R,	# b8-bf
255AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# c0-c7
256AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# c8-cf
257AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# d0-d7
258AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X,	# d8-df
259AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X,	# e0-e7
260AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X,	# e8-ef
261AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# f0-f7
262AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX,	# f8-ff
263};
264buf := array[NBUF] of byte;
265count(f: ref Sys->FD, name: string)
266{
267	state := statesp;
268	nline = nword = nchar = nbadr = nbyte = 0;
269	n := 0;
270	for(;;){
271		n = sys->read(f, buf, NBUF);
272		if(n <= 0)
273			break;
274		nbyte += n;
275		nchar += n;	# might be too large, gets decreased later
276		i := 0;
277		do{
278			case int state[int buf[i++]]{
279			int AC2 =>   state = statec2;
280			int AC2R =>  state = statec2; nchar--;
281			int AC2W =>  state = statec2; nword++;
282			int AC2X =>  state = statec2;          nbadr++;
283			int AC3 =>   state = statec3;
284			int AC3W =>  state = statec3; nword++;
285			int AC3X =>  state = statec3;          nbadr++;
286			int ASP =>   state = statesp;
287			int ASPN =>  state = statesp; nline++;
288			int ASPNX => state = statesp; nline++; nbadr++;
289			int ASPX =>  state = statesp;          nbadr++;
290			int AWD =>   state = statewd;
291			int AWDR =>  state = statewd; nchar--;
292			int AWDW =>  state = statewd; nword++;
293			int AWDWX => state = statewd; nword++; nbadr++;
294			int AWDX =>  state = statewd;          nbadr++;
295			}
296		}while(i < n);
297	}
298	if(state!=statesp && state!=statewd)
299		nbadr++;
300	if(n < 0)
301		sys->fprint(stderr, "wc: error reading %s: %r\n", name);
302	report(nline, nword, nchar, nbadr, nbyte, name);
303}
304