/*	@(#)expand.c	2.1		*/
#
/*
 *	UNIX shell
 *
 *	S. R. Bourne
 *	Bell Telephone Laboratories
 *
 */

#include	"defs.h"
#include	<sys/types.h>
#define DIRSIZ 15
#include	<sys/stat.h>
#include	<sys/dir.h>



/* globals (file name generation)
 *
 * "*" in params matches r.e ".*"
 * "?" in params matches r.e. "."
 * "[...]" in params matches character class
 * "[...a-z...]" in params matches a through z.
 *
 */

PROC VOID	addg();


INT	expand(as,rflg)
	STRING		as;
{
	INT		count, dirf;
	BOOL		dir=0;
	STRING		rescan = 0;
	REG STRING	s, cs;
	ARGPTR		schain = gchain;
	struct direct	entry;
	STATBUF		statb;
	REG INT		i;

	IF trapnote&SIGSET THEN return(0); FI

	s=cs=as; entry.d_name[DIRSIZ-1]=0; /* to end the string */

	/* check for meta chars */
	BEGIN
	   REG BOOL slash, open; slash=0;open=0;
	   REP
		SWITCH *cs++ IN
		case 0:		IF rflg ANDF slash THEN break;
				ELSE return(0);
				FI

		case '/':	slash++;
				open = 0;
				continue;

		case '[':	open++;
				continue;

		case ']':	IF open THEN break FI
				continue;

		case '?':
		case '*':
				cs--;
				break;
		default:	continue;
		ENDSW
		break;
	   PER TRUE DONE
	END

	LOOP	IF cs==s
		THEN	s=nullstr;
			break;
		ELIF *--cs == '/'
		THEN	*cs=0;
			IF s==cs THEN s="/" FI
			break;
		FI
	POOL
	chgquot(s, 0);
	IF stat(*s?s:".",&statb)>=0
	    ANDF (statb.st_mode&S_IFMT)==S_IFDIR
	    ANDF (dirf=open(*s?s:".",0))>0
	THEN	dir++;
	FI
	chgquot(s, 1);
	count=0;
	IF *cs==0 THEN *cs++=0200 FI
	IF dir
	THEN	/* check for rescan */
		REG STRING pm;
		REG STRING rs; rs=cs;

		REP	IF *rs=='/' THEN rescan=rs; *rs=0; gchain=0 FI
		PER	*rs++ DONE

		WHILE read(dirf, &entry, 16) == 16 ANDF (trapnote&SIGSET) == 0
		DO	IF entry.d_ino==0 ORF
			    (*entry.d_name=='.' ANDF *cs!='.')
			THEN	continue;
			FI
/*
 *	Here lies the fix for the "echo * /." problem when
 *	there are files with metacharacters in there names.
 */
			chgquot(entry.d_name, 1);
			IF gmatch(entry.d_name, cs)
			THEN	addg(s,entry.d_name,rescan); count++;
			FI
			chgquot(entry.d_name, 0);
		OD
		close(dirf);

		IF rescan
		THEN	REG ARGPTR	rchain;
			rchain=gchain; gchain=schain;
			IF count
			THEN	count=0;
				WHILE rchain
				DO	count += expand(rchain->argval,1);
					rchain=rchain->argnxt;
				OD
			FI
			*rescan='/';
		FI
	FI

	BEGIN
	   REG CHAR	c;
	   s=as;
	   WHILE c = *s
	   DO	*s++=(c&STRIP?c:'/') OD
	END
	return(count);
}

gmatch(s, p)
	REG STRING	s, p;
{
	REG INT		scc;
	CHAR		c;

	IF scc = *s++
	THEN	IF (scc &= STRIP)==0
		THEN	scc=0200;
		FI
	FI
	SWITCH c = *p++ IN

	    case '[':
		{BOOL ok; INT lc; INT notflag=0;
		ok=0; lc=077777;
		IF *p == '!' THEN notflag=1; p++; FI
		WHILE c = *p++
		DO	IF c==']'
			THEN	return(ok?gmatch(s,p):0);
			ELIF c==MINUS
			THEN	IF notflag
				THEN	IF lc>scc ORF scc>*(p++)
					THEN ok++;
					ELSE return(0)
					FI
				ELSE IF lc<=scc ANDF scc<=(*p++) THEN ok++ FI
				FI
			ELSE	IF notflag
				THEN	IF scc!=(lc=(c&STRIP))
					THEN ok++;
					ELSE return(0)
					FI
				ELSE IF scc==(lc=(c&STRIP)) THEN ok++ FI
				FI
			FI
		OD
		return(0);
		}
	    default:
		IF (c&STRIP)!=scc THEN return(0) FI

	    case '?':
		return(scc?gmatch(s,p):0);

	    case '*':
		IF *p==0 THEN return(1) FI
		--s;
		WHILE *s
		DO  IF gmatch(s++,p) THEN return(1) FI OD
		return(0);

	    case 0:
		return(scc==0);
	ENDSW
}

LOCAL VOID	addg(as1,as2,as3)
	STRING		as1, as2, as3;
{
	REG STRING	s1, s2;
	REG INT		c;

	s2 = locstak()+BYTESPERWORD;

	s1=as1;
	WHILE c = *s1++
	DO	IF (c &= STRIP)==0
		THEN	*s2++='/';
			break;
		FI
		*s2++=c;
	OD
	s1=as2;
	WHILE *s2 = *s1++ DO s2++ OD
	IF s1=as3
	THEN	*s2++='/';
		WHILE *s2++ = *++s1 DONE
	FI
	makearg(endstak(s2));
}

makearg(args)
	REG STRING	args;
{
	((ARGPTR) args)->argnxt=gchain;
	gchain=(ARGPTR) args;
}

chgquot(str, flg)
REG STRING	str;
REG INT		flg;
{
	REG INT i;

	FOR i=0;i<DIRSIZ;i++
	DO
		SWITCH str[i]  IN
		case '\0':
			return;
		case '*':
		case '?':
		case '[': 
		case '*'|0200:
		case '?'|0200:
		case '['|0200: 
			IF flg==0
			THEN
				str[i] &= (~QUOTE);
			ELSE
				str[i] |= QUOTE;
			FI
		ENDSW
	OD
}
