aboutsummaryrefslogtreecommitdiff
path: root/sys/fio/fntgfn.x
blob: 3f2ba5de99cf09f4f2950f0ef8b18541b7c71f1a (plain) (blame)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.

include	<ctype.h>
include	<chars.h>
include	<pattern.h>
include	<syserr.h>
include	<diropen.h>

.help fntgfn
.nf _________________________________________________________________________
File Name Template Package

This package contains routines to expand a file name template string into a
list of file names, and to access the individual elements of the list.  The
template is a list of file names, patterns, and/or list file names.  The
concatenation operator may be used within input list elements to form new
output filenames.  String substitution may also be used to form new filenames.

Sample template string:

	alpha, *.x, data* // .pix, [a-m]*, @list_file

This template would be expanded as the file "alpha", followed in successive
calls by all the files in the current directory whose names end in ".x",
followed by all files whose names begin with "data" with the extension ".pix"
appended, and so on.  The @ character signifies a list file (file containing
regular file names).

String substitution uses the first string given for the template, expands
the template, and for each filename generated by the template, substitutes
the second string to generate a new filename.  Some examples follow.

	*.%x%y%			change extension to `y'
	*%%_abc%.imh		append `_abc' to root
	nite%1%2%.1024.imh	change `nite1' to `nite2'

Main entry points:

	fntopnb - expand template and open a buffered filename list
	fntgfnb - get next filename from buffered list (sequential)
	fntrfnb - get next filename from buffered list (random)
	fntclsb - close buffered list
	fntlenb - get number of filenames in a buffered list
	fntrewb - rewind the list

Low Level Entry Points:

	 fntopn - open an unbuffered filename list
	 fntgfn - get next filename from unbuffered list
	 fntcls - close unbuffered list

The B suffix routines are the highest level and most convenient to use.
The remaining routines expand a template "on the fly" and do not permit
sorting or determination of the length of the list.
.endhelp ____________________________________________________________________

# FNTB descriptor structure.
define	LEN_FNTBHDR		5
define	FNTB_MAGIC		5164
define	B_MAGIC			Memi[$1]
define	B_SBUFPTR		Memi[$1+1]	# string buffer pointer
define	B_NSTR			Memi[$1+2]	# number of strings
define	B_STRNUM		Memi[$1+3]	# used to read list
define	B_STRINDX		Memi[$1+$2-1+4]	# index of string

# FNTU descriptor structure.
define	LEN_FNTUHDR		(10+1024+256)
define	FNTU_MAGIC		5664
define	U_MAGIC			Memi[$1]
define	U_FILDES		Memi[$1+1]
define	U_TEMPLATE		Memi[$1+2]	# pointer
define	U_TEMPLATE_INDEX	Memi[$1+3]
define	U_PATTERN		(P2C($1+10))
define	U_LDIR			(P2C($1+1034))

# Special characters and size limiting definitions.
define	TOK_DELIM		','		# token delimiter
define	LIST_FILE_CHAR		'@'		# @listfile
define	CH_EDIT			'%'		# string substitution metachar
define	SZ_PATTERN		1023
define	SZ_LDIR			255
define	SZ_PATSTR		1023
define	MAX_EDIT		8
define	MAX_PATTERNS		8

# Tokens.
define	EO_TEMPLATE		1
define	LIST_FILE		2
define	PATTERN_STRING		3
define	FILE_NAME		4

# Size limiting definitions (initial buffer sizes).
define	SZ_DEFSTRBUF		2048		# default string buffer size
define	LEN_INDEXVECTOR		256		# initial length of index vector


# FNTOPNB -- General open buffered list routine, for any type of filename list.
# Expand template into string buffer, sort if so indicated.

int procedure fntopnb (template, sort)

char	template[ARB]		# filename template
int	sort			# sort expanded patterns

int	nedit[MAX_PATTERNS], junk, nchars
bool	is_template[MAX_PATTERNS], is_edit[MAX_PATTERNS], sortlist, is_url
pointer	sp, pbuf, fname, rname, extn, ebuf, sbuf, list, ip, op, ep, pp
pointer	patp[MAX_PATTERNS], flist[MAX_PATTERNS], editp[MAX_EDIT]
int	nlists, npat, nstr, maxstr, nextch, sz_sbuf, ix, first_string, ch, i
int	fntopn(), fntgfn(), fnt_getpat(), gstrcpy(), fnt_edit(), stridx()
int	patmake(), patmatch(), strncmp()
errchk	fntopn, fntgfn, syserr, malloc, realloc

begin
	call smark (sp)
	call salloc (rname, SZ_FNAME, TY_CHAR)
	call salloc (fname, SZ_FNAME, TY_CHAR)
	call salloc (extn,  SZ_FNAME, TY_CHAR)
	call salloc (pbuf,  SZ_LINE, TY_CHAR)
	call salloc (ebuf,  SZ_LINE, TY_CHAR)

	# Allocate list descriptor.
	call malloc (list, LEN_FNTBHDR + LEN_INDEXVECTOR, TY_INT)
	call malloc (sbuf, SZ_DEFSTRBUF, TY_CHAR)

	B_MAGIC(list) = FNTB_MAGIC
	maxstr = LEN_INDEXVECTOR
	sz_sbuf = SZ_DEFSTRBUF
	nextch = 1				# offset into string buffer
	nstr = 0

	# Read the file names into the string buffer.  Dynamically adjust
	# the size of the string buffer and/or index vector as necessary.
	# There must always be at least SZ_FNAME chars left in the string
	# buffer.  The outer loop is over comma delimited fields of the
	# filename template.  The inner loop is over individual filenames.

	ix = 1
	while (fnt_getpat (template, ix, patp, npat, pbuf, SZ_LINE) > 0) {
	    first_string = nstr + 1
	    sortlist = (sort == YES)
	    nlists = 0
	    ep = ebuf

	    # Each piece of the current comma delimited template may consist
	    # of several sublists to be independently expanded and concatenated
	    # to form each output filename.  The lists must either be degenerate
	    # (a simple string) or actual lists to be expanded with FNTOPN.

	    do i = 1, npat {
		is_template[i] = false
		is_edit[i] = false
		nedit[i] = 0
		op = patp[i]

		# Examine sublist to see if it is a template or a string
		# constant.  If template, open file list.  Template
		# metacharacters may be escaped to be included in filenames.
		# If the pattern contains edit substitution sequences it
		# must be processed to remove the substitution strings.

		is_url = false
		for (ip=op;  Memc[ip] != EOS;  ip=ip+1) {
		    ch = Memc[ip]

		    if (ch == ':' && strncmp (Memc[ip+1], "//", 2) == 0) {
			# URL string.
			is_template[i] = false
			is_edit[i] = false
			is_url = true
		    } else if (!is_url && stridx (Memc[ip], "@*?[%") > 0) {
			if (ip > patp[i] && Memc[ip-1] == '\\') {
			    Memc[op-1] = ch
			    ip = ip + 1
			    ch = Memc[ip]
			} else if (ch == CH_EDIT) {
			    is_edit[i] = true
			} else {
			    if (ch == '@' && op == ip)
				sortlist = false
			    if (!is_url)
			        is_template[i] = true
			}
		    }

		    Memc[op] = ch
		    op = op + 1
		}

		Memc[op] = EOS

		# Open filename template if pattern contained metacharacters.
		# A string constant containing edit string substitution is a
		# special case, eg. "file%%_2%.ext".

		if (is_template[i] || is_edit[i]) {
		    editp[i] = ep
		    call fnt_mkpat (Memc[patp[i]], Memc[fname], SZ_FNAME,
			ep, nedit[i])
		    flist[i] = fntopn (Memc[fname])

		    # In the case of a string constant edit we do not really
		    # have a file template, but we open one anyhow just to
		    # make use of the common code and the descriptor.

		    if (!is_template[i]) {
			# Encode the pattern (containing the %%).
			junk = patmake (Memc[fname], Memc[U_PATTERN(flist[i])],
			    SZ_PATTERN)

			# Strip the %% from the pattern, leaving the "input"
			# filename in patp[i].

			op = patp[i]
			for (ip=fname;  Memc[ip] != EOS;  ip=ip+1)
			    if (Memc[ip] != CH_EDIT) {
				Memc[op] = Memc[ip]
				op = op + 1
			    }
			Memc[op] = EOS

			# Now match the stripped pattern against the %%
			# pattern.  This sets up U_PATTERN for the edit.

			junk = patmatch (Memc[patp[i]],
			    Memc[U_PATTERN(flist[i])])
		    } else
			nlists = nlists + 1
		}
	    }

	    # Expand the template into a sequence of filenames in the string
	    # buffer, saving the indices of the list elements in the STRINDX
	    # array.  Reallocate a larger buffer if necessary.  If the sublists
	    # are not all the same length the shortest list will terminate the
	    # output list.

	    repeat {
		# Concatenate the next element from each sublist; the sublists
		# may be either real lists or string constants.  Concatenate
		# only to the root filename.

		Memc[extn] = EOS
		op = fname

		do i = 1, npat {
		    # Save first extension field encountered and set op to
		    # end of root.

		    if (Memc[extn] == EOS)
			for (ip=op-1;  ip > fname;  ip=ip-1)
			    if (Memc[ip] == '.') {
				call strcpy (Memc[ip], Memc[extn], SZ_FNAME)
				op = ip
				break
			    }

		    # Concatenate the next file element.  This can be either a
		    # file name from a file template, a constant file name from
		    # a string edit expression, or a simple string constant.

		    if (!is_url && (is_template[i] || is_edit[i])) {
			ip = rname
			pp = flist[i]
			if (is_template[i]) {
			    if (fntgfn (pp, Memc[rname], SZ_FNAME) == EOF) {
				op = fname
				break

			    } else if (U_FILDES(pp) != NULL) {
				# Reading from a directory or list; set offset
				# of substring to be edited to exclude any
				# ldir prefix, since this will not have been
				# used for the pattern match.

				nchars = gstrcpy (Memc[U_LDIR(pp)],Memc[op],ARB)
				op = op + nchars
				ip = ip + nchars
			    }
			} else
			    call strcpy (Memc[patp[i]], Memc[rname], SZ_FNAME)

			op = op + fnt_edit (Memc[ip], Memc[op], editp[i],
			    nedit[i], Memc[U_PATTERN(pp)])

		    } else {
			op = op + gstrcpy (Memc[patp[i]], Memc[op], ARB)
		    }
		}

		# End of list if nothing returned.
		if (op == fname)
		    break

		# Tack extension back on.
		if (Memc[extn] != EOS)
		    op = op + gstrcpy (Memc[extn], Memc[op], ARB)

		# Need more room for list element pointers?
		nstr = nstr + 1
		if (nstr > maxstr) {
		    maxstr = maxstr + LEN_INDEXVECTOR
		    call realloc (list, LEN_FNTBHDR + maxstr, TY_INT)
		}

		# Out of space in string buffer?
		if (nextch + (op - fname) >= sz_sbuf) {
		    sz_sbuf = sz_sbuf + SZ_DEFSTRBUF
		    call realloc (sbuf, sz_sbuf, TY_CHAR)
		}

		# Save index of list element, move chars to string buffer.
		# Allow space for the EOS after each string.

		B_STRINDX(list,nstr) = nextch
		nextch = nextch +
		    gstrcpy (Memc[fname], Memc[sbuf+nextch-1], ARB) + 1

	    } until (nlists == 0)

	    do i = 1, npat
		if (is_template[i] || is_edit[i])
		    call fntcls (flist[i])

	    # If sorting is desired and the pattern did not specify an explicit
	    # list (e.g., "@listfile"), sort the last batch of filenames.

	    if (sortlist && nstr > first_string)
		call strsrt (B_STRINDX(list,first_string), Memc[sbuf],
		    nstr - first_string + 1)
	}

	# Update the string buffer descriptor, return unused buffer space.
	# Rewind the list in preparation for reading (set strnum=1).

	call realloc (sbuf, nextch, TY_CHAR)
	call realloc (list, LEN_FNTBHDR + nstr, TY_INT)

	B_NSTR(list)	= nstr
	B_STRNUM(list)	= 1
	B_SBUFPTR(list)	= sbuf

	call sfree (sp)
	return (list)
end


# FNT_MKPAT -- Take a pattern string possibly containing %a%b% string
# substitution sequences, returning a pattern string as required for PATMAKE,
# and a sequence of substitution strings for later use by FNT_EDIT to edit
# filenames matched by FNTGFN.

procedure fnt_mkpat (pat, patstr, maxch, ep, nedit)

char	pat[ARB]		# pattern with embedded substitution sequences
char	patstr[maxch]		# receives pattern as req'd by PATMAKE
int	maxch
pointer	ep			# where to put substitution string chars
int	nedit			# number of substitution chars

int	nhat
int	ip, op

begin
	nedit = 0
	nhat  = 0
	op    = 1

	for (ip=1;  pat[ip] != EOS;  ip=ip+1) {
	    if (pat[ip] == CH_EDIT) {
		if (ip > 1 && pat[ip-1] == '\\') {
		    # Moved escaped metacharacter to pattern string.
		    patstr[op] = pat[ip]
		    op = op + 1

		} else if (nhat > 0) {
		    # Copy substitution string to ebuf.
		    patstr[op] = pat[ip]
		    op = op + 1
		    nedit = nedit + 1

		    ip = ip + 1
		    while (pat[ip] != EOS && pat[ip] != CH_EDIT) {
			Memc[ep] = pat[ip]
			ep = ep + 1
			ip = ip + 1
		    }

		    Memc[ep] = EOS
		    ep = ep + 1
		    if (pat[ip] == EOS)
			ip = ip - 1
		    nhat = 0

		} else {
		    patstr[op] = pat[ip]
		    op = op + 1
		    nhat = nhat + 1
		}

	    } else {
		patstr[op] = pat[ip]
		op = op + 1
		if (op > maxch)
		    break
	    }
	}

	patstr[op] = EOS
end


# FNT_EDIT -- Perform string substitution on a matched filename, using the
# list of substitution strings written by FNT_MKPAT, the first of which is
# pointed to by EDITP.  The regions to be replaced were marked symbolically
# by the CH_EDIT characters in the user supplied pattern.  The actual indices
# of these regions depend upon the actual filename and are saved by the
# pattern matching code in the encoded pattern buffer PATBUF, for retrieval
# by PATINDEX.  Carry out the substitution and return the length of the
# output string as the function argument.

int procedure fnt_edit (in, out, editp, nedit, patbuf)

char	in[ARB]			# input string to be edited
char	out[ARB]		# receives edited string
pointer	editp			# pointer to first substitution string
int	nedit			# number of edits required
char	patbuf[ARB]		# encoded pattern

pointer	ep
int	ip1, ip2, ip, op, i
int	patindex()

begin
	ep = editp - 1
	ip = 1
	op = 1

	do i = 1, nedit {
	    # Get indices of first and last+1 characters to be substituted for
	    # in the input string.

	    ip1 = patindex (patbuf, (i-1) * 2 + 1)
	    ip2 = patindex (patbuf, (i-1) * 2 + 2)
	    if (ip1 == 0 || ip2 == 0 || ip1 > ip2)
		break		# cannot happen

	    # Copy up to first char to be replaced.
	    for (;  ip < ip1;  ip=ip+1) {
		out[op] = in[ip]
		op = op + 1
	    }

	    # Append substitution string.
	    for (ep=ep+1;  Memc[ep] != EOS;  ep=ep+1) {
		out[op] = Memc[ep]
		op = op + 1
	    }

	    # Continue at character IP2 in the input string.
	    ip = ip2
	}

	# Copy remainder of input string to the output string.
	for (;  in[ip] != EOS;  ip=ip+1) {
	    out[op] = in[ip]
	    op = op + 1
	}

	out[op] = EOS
	return (op - 1)
end


# FNTGFNB -- Return the next filename from the list.

int procedure fntgfnb (list, fname, maxch)

pointer	list			# list descriptor pointer
char	fname[ARB]		# output filename
int	maxch

pointer	strptr
int	file_number
int	gstrcpy()
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	file_number = B_STRNUM(list)
	if (file_number > B_NSTR(list))
	    return (EOF)
	else {
	    B_STRNUM(list) = file_number + 1
	    strptr = B_SBUFPTR(list) + B_STRINDX(list,file_number) - 1
	    return (gstrcpy (Memc[strptr], fname, maxch))
	}
end


# FNTRFNB -- Return the indexed filename from the list.  For applications
# which need to access the list at random.  Returns len(fname) or EOF for
# references to nonexistent list elements.

int procedure fntrfnb (list, index, fname, maxch)

pointer	list			# list descriptor pointer
int	index			# index of list element to be returned
char	fname[ARB]		# output filename
int	maxch

pointer	strptr
int	gstrcpy()
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	if (index < 1 || index > B_NSTR(list))
	    return (EOF)
	else {
	    strptr = B_SBUFPTR(list) + B_STRINDX(list,index) - 1
	    return (gstrcpy (Memc[strptr], fname, maxch))
	}
end


# FNTCLSB -- Close a buffered list and return all storage.

procedure fntclsb (list)

pointer	list			# list descriptor pointer
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	call mfree (B_SBUFPTR(list), TY_CHAR)
	call mfree (list, TY_INT)
end


# FNTREWB -- Rewind a buffered filename list.

procedure fntrewb (list)

pointer	list			# list descriptor pointer
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	B_STRNUM(list) = 1
end


# FNTLENB -- Return the number of filenames in the list.

int procedure fntlenb (list)

pointer	list			# list descriptor pointer
errchk	syserr

begin
	if (B_MAGIC(list) != FNTB_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	return (B_NSTR(list))
end


# FNT_GETPAT -- Return the next comma delimited field from the template string
# with any leading or trailing whitespace stripped off.  The field may consist
# of a simple string constant, a filename template, or a sequence of either
# delimited by concatenation operators //.  We do not make any distinction here
# between string constants and patterns; return the \ with all escape sequences
# as this will be stripped by the higher level code if used to include pattern
# matching metacharacters in filenames.

int procedure fnt_getpat (template, ix, patp, npat, sbuf, maxch)

char	template[ARB]		# template from which to extract field
int	ix			# next char in template
pointer	patp[MAX_PATTERNS]	# receives pointers to sublists (patterns)
int	npat			# receives number of PATP elements set
pointer	sbuf			# used to store output strings
int	maxch			# maxch chars out

int	ch, peek
bool	is_url
pointer	op

int	strncmp(), stridx()
errchk	syserr

begin
	while (IS_WHITE(template[ix]) || template[ix] == ',')
	    ix = ix + 1

	patp[1] = sbuf
	npat = 1
	op = sbuf
	is_url = false

	#for (ch=template[ix];  ch != EOS && ch != ',';  ch=template[ix]) {
	for (ch=template[ix];  ch != EOS;  ch=template[ix]) {
	    peek = template[ix+1]
	    if (IS_WHITE (ch)) {
		# Ignore all whitespace.
		ix = ix + 1
		next

	    } else if ((is_url && ch == ',')) {
		if (stridx (peek, "+-.0123456789") == 0) {
		    break
		} else {
		    # Keep a comma in a URL followed by a digit
		    Memc[op] = ','
		    op = op + 1
		    ix = ix + 1
		}

	    } else if (!is_url && ch == ',') {
		break

	    } else if (ch == '\\' && template[ix+1] == ',') {
		# Escape a comma.
		Memc[op] = ','
		op = op + 1
		ix = ix + 2

	    } else if (!is_url && (ch == '/' && template[ix+1] == '/')) {
		# Concatenation operator: start a new sublist.
		Memc[op] = EOS
		op = op + 1
		ix = ix + 2
		npat = npat + 1
		if (npat > MAX_PATTERNS)
		    call syserr (SYS_FNTMAXPAT)
		patp[npat] = op

	    } else if (ch == ':' && strncmp ("//", template[ix+1], 2) == 0) {
		# Start of URL string, deposit in output list.
		Memc[op] = ch
		op = op + 1
		ix = ix + 1
		is_url = true

	    } else {
		# Ordinary character, deposit in output list.
		Memc[op] = ch
		op = op + 1
		ix = ix + 1
	    }

	    if (op - sbuf > maxch)
		break
	}

	Memc[op] = EOS
	return (op - sbuf)
end


# FNTGFN -- Get the next file name from the named parameter (template).
# This is the guy that does all the work.  A file name may be selected from
# a directory file or list file by pattern matching, or may come from the
# template list string itself.

int procedure fntgfn (pp, outstr, maxch)

pointer	pp			# pattern pointer
char	outstr[ARB]		# output filename
int	maxch

bool	match
pointer	ip, sp, linebuf, fname, patstr
int	nchars, token, first_ch, last_ch, status

bool	streq()
int	getline(), gpatmatch(), patmake(), nowhite(), gstrcat()
int	fnt_read_template(), fnt_open_list()
errchk	salloc, getline, close, fnt_open_list, syserr

begin
	if (pp == NULL || U_MAGIC(pp) != FNTU_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	call smark (sp)					# get buffers
	call salloc (linebuf, SZ_LINE, TY_CHAR)
	call salloc (patstr, SZ_PATSTR, TY_CHAR)
	call salloc (fname, SZ_PATHNAME, TY_CHAR)

	repeat {
	    # Read file names from either list file or directory file, until
	    # one is found which matches pattern, or until EOF is reached.
	    # Make sure pattern matches the ENTIRE file name string, rather
	    # than a substring.

	    if (U_FILDES(pp) != NULL) {			# reading from a file?
		while (getline (U_FILDES(pp), Memc[linebuf]) != EOF) {
		    for (ip=linebuf;  IS_WHITE (Memc[ip]);  ip=ip+1)
			;
		    nchars = nowhite (Memc[ip], Memc[fname], maxch)
		    if (nchars == 0)			# skip blank lines
			next

		    # If the encoded pattern is the null string match anything.
		    if (Memc[U_PATTERN(pp)] == EOS) {
			match = true
		    } else if (gpatmatch (Memc[fname], Memc[U_PATTERN(pp)],
			first_ch, last_ch) > 0) {
			match = (first_ch == 1 && last_ch == nchars)
		    } else
			match = false

		    if (match) {
			call strcpy (Memc[U_LDIR(pp)], outstr, maxch)
			nchars = gstrcat (Memc[fname], outstr, maxch)
			call sfree (sp)
			return (nchars)
		    }
		}

		call close (U_FILDES(pp))
		U_FILDES(pp) = NULL
	    }

	    switch (fnt_read_template (pp, Memc[linebuf], SZ_LINE, token)) {
	    case EO_TEMPLATE:
		nchars = EOF
		outstr[1] = EOS
		call sfree (sp)
		return (nchars)

	    case LIST_FILE, PATTERN_STRING:
		# Break the pattern string into a list file or directory
		# name and a pattern.

		if (token == PATTERN_STRING) {
		    Memc[patstr] = '^'
		    ip = patstr + 1
		} else
		    ip = patstr

		U_FILDES(pp) = fnt_open_list (Memc[linebuf], Memc[ip],
		    SZ_PATSTR-1, Memc[fname], Memc[U_LDIR(pp)], token)

		# Encode the pattern.  If the pattern is matchall set encoded
		# pattern string to NULL and pattern matching will be skipped.

		if (streq (Memc[patstr], "?*"))
		    Memc[U_PATTERN(pp)] = EOS
		else {
		    status = patmake (Memc[patstr], Memc[U_PATTERN(pp)],
			SZ_PATTERN)
		    if (status == ERR)
			call syserr (SYS_FNTBADPAT)
		}
		
	    default:					# simple file name
		nchars = nowhite (Memc[linebuf], outstr, maxch)
		if (nchars > 0) {
		    call sfree (sp)
		    return (nchars)
		}
	    }
	}
end


# FNT_READ_TEMPLATE -- Get next token from template string, return integer
# code identifying the type of token.

int procedure fnt_read_template (pp, outstr, maxch, token)

pointer	pp				#I pointer to param descriptor
char	outstr[maxch]			#O receives token
int	maxch				#I max chars out
int	token				#O token type code

int	nseen, i
pointer	ip, ip_start, op, cp
int	stridx(), strncmp()

begin
	ip = U_TEMPLATE_INDEX(pp)			# retrieve pointer
	while (IS_WHITE (Memc[ip]))
	    ip = ip + 1


	switch (Memc[ip]) {
	case EOS:
	    op = 1
	    token = EO_TEMPLATE

	case LIST_FILE_CHAR:				# list file spec
	    ip = ip + 1					# skip the @
	    for (op=1;  Memc[ip] != TOK_DELIM && Memc[ip] != EOS;  op=op+1) {
		outstr[op] = Memc[ip]
		ip = ip + 1
	    }
	    token = LIST_FILE
	    if (Memc[ip] == TOK_DELIM)
		ip = ip + 1

	default:					# fname or pat string
	    token = FILE_NAME
	    # Extract token.  Determine if regular file name or pattern string.
	    # Disable metacharacters not useful for file name patterns.

	    ip_start = ip
	    for (op=1;  Memc[ip] != EOS;  ip=ip+1) {
		if (Memc[ip] == CH_ESCAPE && Memc[ip+1] != EOS) {
		    # Escape sequence.  Pass both the escape and the escaped
		    # character on to the lower level code.

		    outstr[op] = CH_ESCAPE
		    op = op + 1
		    ip = ip + 1

		} else if (Memc[ip] == TOK_DELIM) {
		    ip = ip + 1
		    break

		} else if (Memc[ip] == FNLDIR_CHAR || Memc[ip] == '/') {
		    token = FILE_NAME

		} else if (Memc[ip] == '*') {
		    # Map "*" into "?*".
		    token = PATTERN_STRING
		    outstr[op] = '?'
		    op = op + 1

		} else if (Memc[ip] == '%') {
		    # The % metacharacter must appear twice (not three times,
		    # as the high level code strips the subsitution field) to
		    # be recognized as the pattern substitution metacharacter.

		    nseen = 0
		    do i = 1, ARB {
			cp = ip_start + i - 1
			if (Memc[cp] == EOS || Memc[cp] == TOK_DELIM)
			    break
			else if (Memc[cp] == '%' && Memc[cp-1] != '\\')
			    nseen = nseen + 1
		    }
		    if (nseen < 2) {
			outstr[op] = CH_ESCAPE
			op = op + 1
		    }
		} else if (stridx (Memc[ip], "[?{") > 0)
		    token = PATTERN_STRING

		outstr[op] = Memc[ip]
		op = op + 1
	    }
	}
	    
	# Remove any trailing whitespace.
	op = op - 1
	while (op > 0 && IS_WHITE (outstr[op]))
	    op = op - 1
	outstr[op+1] = EOS

	if (op > 0)
	    if (outstr[op] == FNLDIR_CHAR || outstr[op] == '/')
		token = PATTERN_STRING

	U_TEMPLATE_INDEX(pp) = ip			# update pointer

	return (token)
end


# FNT_OPEN_LIST -- Open list file or directory.  If reading from a directory,
# open the current directory if a directory name is not given.  Extract
# pattern string (if any), and return in PATSTR.  If no pattern string is
# given, return a pattern which will match all files in the list.

int procedure fnt_open_list (str, patstr, maxch, fname, ldir, ftype)

int	maxch, ftype
char	ldir[SZ_LDIR]
char	str[ARB], patstr[maxch], fname[SZ_FNAME]
int	fd, ip, op, fnt_delim, pat_start, dirmode
int	open(), diropen()
errchk	open, diropen, fpathname

begin
	op = 1
	fnt_delim = NULL
	pat_start = NULL

	# Search for a valid directory prefix.
	for (ip=1;  str[ip] != EOS;  ip=ip+1) {
	    fname[op] = str[ip]
	    if (ftype != LIST_FILE)
		if (fname[op] == FNLDIR_CHAR || fname[op] == '//')
		    if (op == 1 || fname[op-1] != '\\') {
			fnt_delim = op
			pat_start = ip + 1
		    }
	    op = op + 1
	}
	fname[op] = EOS

	if (ftype == LIST_FILE) {
	    if (fnt_delim != NULL)
		fname[fnt_delim] = EOS
	    fd = open (fname, READ_ONLY, TEXT_FILE)
	    ldir[1] = EOS

	} else {
	    if (fnt_delim != NULL)		# specific directory
		fname[fnt_delim+1] = EOS
	    else				# current directory
		fname[1] = EOS
	    call fpathname (fname, ldir, SZ_LDIR)

	    dirmode = SKIP_HIDDEN_FILES
	    if (pat_start != NULL) {
		if (str[pat_start] == '.')
		    dirmode = PASS_HIDDEN_FILES
	    } else if (ftype != LIST_FILE && str[1] == '.')
		dirmode = PASS_HIDDEN_FILES

	    fd = diropen (ldir, dirmode)
	    call strcpy (fname, ldir, SZ_LDIR)
	}

	# If pattern string is appended to list file name, extract
	# it, otherwise set the default pattern "match all" (*).

	op = 1
	if (pat_start != NULL)
	    ip = pat_start
	else if (ftype != LIST_FILE)
	    ip = 1

	for (;  str[ip] != EOS;  ip=ip+1) {
	    patstr[op] = str[ip]
	    op = op + 1
	}

	# No pattern string given, default to "?*".
	if (op == 1) {
	    patstr[1] = CH_ANY
	    patstr[2] = CH_CLOSURE
	    op = 3
	}
	patstr[op] = EOS

	return (fd)
end


# FNTOPN -- Open and initialize the template descriptor.

pointer procedure fntopn (template)

char	template[ARB]

pointer	pp
int	nchars
int	strlen()
errchk	calloc, malloc

begin
	nchars = strlen (template)

	call calloc (pp, LEN_FNTUHDR, TY_STRUCT)
	call malloc (U_TEMPLATE(pp), nchars, TY_CHAR)

	call strcpy (template, Memc[U_TEMPLATE(pp)], nchars)
	U_TEMPLATE_INDEX(pp) = U_TEMPLATE(pp)
	U_MAGIC(pp) = FNTU_MAGIC

	return (pp)
end


# FNTCLS -- Close the template descriptor, return space.

procedure fntcls (pp)

pointer	pp
errchk	syserr

begin
	if (pp == NULL || U_MAGIC(pp) != FNTU_MAGIC)
	    call syserr (SYS_FNTMAGIC)

	if (U_FILDES(pp) != NULL)
	    call close (U_FILDES(pp))

	call mfree (U_TEMPLATE(pp), TY_CHAR)
	call mfree (pp, TY_STRUCT)
end