%  Find - find files and directories.
%  Copyright (C) 1993 Torsten Poulin
%
%  This program is free software; you can redistribute it and/or modify
%  it under the terms of the GNU General Public Licence as published by
%  the Free Software Foundation; either version 2 of the Licence, 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 Licence for more details.
%
%  You should have received a copy of the GNU General Public Licence
%  along with this program; if not, write to the Free Software
%  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
%
%  The author can be contacted by s-mail at
%    Torsten Poulin
%    Banebrinken 99, 2, -77
%    DK-2400 Copenhagen NV
%    Denmark
%
% The original C version of this program was written in six days
% from March 3rd to March 8th, 1993.
%
% $Id: Find.w,v 37.2 93/05/22 14:08:40 Torsten Rel $
% $Log:	Find.w,v $
% Revision 37.2  93/05/22  14:08:40  Torsten
% The informal parts of some of the WEB sections have been changed.
% Apart from a couple of insignificant cases, the code is unchanged.
% 
% Revision 37.1  93/05/12  22:59:47  Torsten
% This is the initial CWEB version of Find.
% 
%
% Here is TeX material that gets inserted after \input cwebmac
\def\(#1){} % this is used to make section names sort themselves better
\def\9#1{} % this is used for sort keys in the index via @@:key}{entry@@>

\outer\def\N#1. \[#2]#3.{\MN#1.\vfil\eject % begin starred section
  \def\rhead{PART #2:\uppercase{#3}} % define running headline
  \message{*\modno} % progress report
  \edef\next{\write\cont{\Z{\?#2]#3}{\modno}{\the\pageno}}}\next
  \ifon\startsection{\bf\ignorespaces#3.\quad}\ignorespaces}
\let\?=\relax % we want to be able to \write a \?

% Redefine the TeX macro used to print the assignment operator
% to print a left arrow instead of an equals sign
\let\K=\gets
\def\FIND{\.{Find}} % The name of the program.
\def\ADOS{{\mc AmigaDOS}} % My operating system of choice

\def\title{Find $
  ($Revision: 37.2 $)
  $}
\def\topofcontents{\null\vfill
  \centerline{\titlefont The {\ttitlefont Find} command}
  \vskip 15pt
  \centerline{$
    ($Revision: 37.2 $)
    $}
  \vglue 0pt plus 1fil minus 1.5in
  \def\?##1]{\hbox to 1in{\hfil##1.\ }}
}
\def\botofcontents{
\vfill\noindent
Copyright \copyright\ 1993 Torsten Poulin
\bigskip\noindent
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public Licence as published by
the Free Software Foundation; either version 2 of the Licence, or (at
your option) any later version.
\smallskip\noindent
This program is distributed in the hope that it will be useful, but
{\mc WITHOUT ANY WARRANTY}; without even the implied warranty of {\mc
MERCHANTABILITY} or {\mc FITNESS FOR A PARTICULAR PURPOSE}. See the
GNU General Public Licence for more details.
\smallskip\noindent
You should have received a copy of the GNU General Public Licence
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
\smallskip\noindent
The author can be contacted by snail-mail at
\smallskip\noindent
\leftline{Torsten Poulin}
\leftline{Banebrinken $99^2$ lejl.\ 77}
\leftline{DK--2400 K{\o}benhavn NV}
\leftline{Denmark}
}

@* \[1] Introduction.
This is \FIND, a program for locating files and directories by
searching the directory hierarchy. It is an \ADOS\ specific version
of the versatile \UNIX\ command with the same name. It is a very
powerful Swiss army knife type of program. Like its \UNIX\ namesake,
it uses a boolean expression supplied on the command line to match the
directory entries.

The command line syntax differs quite a bit from the \UNIX\ version to
make it as compliant with the ``Amiga User Interface Style Guide'' as
possible. Due to this and the fact that \ADOS\ is not a \UNIX\
clone, we cannot say that this program is a subset or a superset of
\UNIX\ \FIND. What we can say is that this version offers somewhat
more flexible printing and that the addition of an ARexx interface
allows the user to do things the author could not possibly anticipate.

@ \FIND\ was written in \Cee\ by me, Torsten Poulin, in March, 1993.
Two months later I decided to become literate and rewrote it in
\.{CWEB} to increase my own chances of still being able to understand
how it works at a future date.

It will require a lot of work to port this program to other operating
systems because it was not written with portability in mind and I
therefore made liberal use of the services provided by the Amiga
operating system. After all, who would't feel tempted on a system
providing more than a thousand ``system calls''? What is worse is that
it will also take some effort to compile it under \ADOS\ if your
favourite \Cee\ compiler happens not to be {\mc SAS}~\Cee\
version~5.10b.

@ Before we go on, we will define new names for some of the Amiga
types, simply because I think they look better that way when {\TeX}ed.
Remember, {\it de gustibus non disputandum.\/}

@d uchar unsigned char
@d ulong unsigned long
@d bool  int
@d address_ptr APTR
@d bcpl_ptr long
@f uchar int
@f ulong int
@f bool  int
@f address_ptr int
@f bcpl_ptr int
@f tok_type int
@f ExprNode int
@d loop for(;;)
@f loop for
@f jmp_buf int

@ We also define some constants. |PROGNAME| is the program name to be
used in error messages. The basename of the public communications port
used by \FIND's ARexx interface is given by |PORTNAME|. The command
supported by the interface is |GETATTR|. The file comments returned by
the |ExAll| function are---the official documentation to the
contrary---\.{BCPL} style strings; i.e., the first byte holds the
length of the string. We therefore skip the first |CMT| bytes of the
comments. |BUFFERLEN| is the length of the general purpose string
|g->scratch|, which should be long enough to hold the longest
possible pathname. Given the \.{BCPL} origin of \.{DOS}, this is
assumed to be 255~characters.  Finally, |ESCCHAR|, |LBRACE|, and
|RBRACE| are the characters used to escape a character, as the left
brace, and the right brace respectively (if the last two are modified,
|TERM| must be changed correspondingly).

The character constants |versionID| and |copyright| are included for
the benefit of programs, like \.{VERSION}, that looks for embedded
messages. They are not used for anything in \FIND. The octal code
|0251| is the \copyright~symbol in the ISO Latin~1 character set.

@d PROGNAME "Find"
@d PORTNAME "TPFIND."
@d GETATTR "getattr"
@d CMT 1
@d BUFFERLEN 512L
@d ESCCHAR '*'
@d LBRACE  '{'
@d RBRACE  '}'
@d TERM    " \t{}"

@<Embedded strings@>=
char const versionID[] = VERSTAG;
char const copyright[] = "$COPYRIGHT:Copyright \251 1993 Torsten Poulin$";

@ The program has the following general outline. It will be filled out
later.

@c
@<Global |#include|s@>@/
@<Global |typedef|s@>@/
@<Pseudo-global variables@>@/
@<Global function prototypes@>@/
@<Lookup table for the lexical scanner function |get_token|@>@/
@<Embedded strings@>@/
@<The |entrypoint| function@>@/

@ @<Global |type...@>=
typedef enum {
  TOK_UNKNOWN, TOK_EOS,
  TOK_LB, TOK_RB, TOK_OR, TOK_AND, TOK_NOT,
  TOK_NAME, TOK_PRINT, TOK_EXEC, TOK_EXECOK, TOK_SIZE,
  TOK_DAYS, TOK_SINCE, TOK_UPTO, TOK_TYPE, TOK_PRUNE,
  TOK_REXX, TOK_FLAGS, TOK_COMMENT, TOK_LFORMAT,
  TOK_EQ, TOK_GT, TOK_LT,
  TOK_FILE, TOK_LFILE, TOK_DIR, TOK_LDIR, TOK_SOFT
} tok_type;

@ Because \FIND\ is written to be reentrant (``pure'' in Amiga
parlance) so it can be made resident, we cannot use global variables.
For convenience, we put some variables in the following structure,
which will be dynamically allocated by the |entrypoint| function. We
then pass around a pointer to that structure, effectively achieving
the effect of global variables.

@<Pseudo...@>=
typedef struct {
  struct DosLibrary *DOSBase;
  struct Library *UtilityBase;
  struct Library *IntuitionBase;
  struct Library *RexxSysBase;
  struct Remember *RememberKey;
  ExprNode *first_node; /* Start of the expression tree */
  bcpl_ptr cdlock; /* Lock on user's current dir */
  long days; /* Today */
  bool prune; /* Controls if a subdirectory should be entered */
  bool depth; /* Controls how directories are matched */
  bool NoRexx; /* Halt execution if |TRUE| (ARexx not accesible) */
  jmp_buf jmp_save; /* Needed by |setjmp|/|longjmp| */
  uchar scratch[BUFFERLEN]; /* Holds temporary strings */
} Global;


@* \[2] Main program.
The |entrypoint| function is the equivalent of the |main| function
used in ``normal'' \Cee\ programs. It opens the shared libraries
needed by \FIND, allocates the structure holding the ``global''
variables, and parses the commandline arguments according to
|TEMPLATE|. Finally, it frees all resources and exits. Note that we
do not check whether the ARexx library was opened successfully.  This
is done when and if ARexx is actually used. That way, it is possible to
use \FIND\ on a machine without ARexx.

@d TEMPLATE "DIR/A,DEPTH/S,EXPRESSION/F/A"
@d OPT_DIR   0
@d OPT_DEPTH 1
@d OPT_EXPR  2
@d ARGC 3

@<The |entrypoint| function@>=
long entrypoint(void)
{
  struct DosLibrary *DOSBase;
  struct Library *IntuitionBase;
  struct RDArgs *args;
  struct DateStamp datestamp;
  Global *g;
  long   arg[ARGC], rc = RETURN_OK;
  
  if (!(DOSBase = (struct DosLibrary *)	OpenLibrary("dos.library", 37L)))
    return RETURN_FAIL;

  if (!(g = AllocVec(sizeof(Global), MEMF_ANY | MEMF_CLEAR)))
    rc = ERROR_NO_FREE_STORE;
  else {
    if (!(g->UtilityBase = OpenLibrary("utility.library", 37L)))
      rc = RETURN_FAIL;
    else {
      if (!(IntuitionBase = OpenLibrary("intuition.library", 37L)))
	rc = RETURN_FAIL;
      else {
	g->RexxSysBase = OpenLibrary("rexxsyslib.library", 0L);
	g->DOSBase = DOSBase; g->IntuitionBase = IntuitionBase;
        @<Initialize variables in |g|@>@;@/
        @<Parse the commandline arguments and execute the expression@>@;@/
	FreeRemember(&g->RememberKey, TRUE);
	if (g->RexxSysBase) CloseLibrary(g->RexxSysBase);
	CloseLibrary(g->IntuitionBase);
      }
      CloseLibrary(g->UtilityBase);
    }
    FreeVec(g);
  }
  rc = printErrorMsg(rc, PROGNAME, DOSBase);
  CloseLibrary((struct Library *) DOSBase);
  return rc;
}

@ We have to initialize a few of the members of |g|. Strictly
speaking, it is only necessary to initialize |g->days| because
|g| was allocated with the |MEMF_CLEAR| option.

@<Init...@>=
DateStamp(&datestamp);
g->days = datestamp.ds_Days;
g->prune = g->NoRexx = FALSE;
g->RememberKey = NULL;

@ This code parses the commandline arguments. The expression string
pointed to by |arg[OPT_EXPR]| is fed to |expr_compiler|. We then
obtain a lock on the user's current directory (needed if we are
instructed to execute an external command) and call |scan_dirs|.

@<Parse the commandline...@>=
arg[OPT_DIR] = arg[OPT_DEPTH] = arg[OPT_EXPR] = 0L;
if (!(args = ReadArgs(TEMPLATE, arg, NULL))) rc = RETURN_FAIL;
else {
  g->depth = (bool) arg[OPT_DEPTH];
  g->first_node = expr_compiler((uchar *) arg[OPT_EXPR], &rc, g);
  if (!(g->cdlock = Lock("", SHARED_LOCK))) rc = RETURN_FAIL;
  else {
    uchar *dname = (uchar *) arg[OPT_DIR];
    if (g->first_node) rc = scan_dirs(dname, dname, g);
    UnLock(g->cdlock);
    if (g->NoRexx)
      MyPrintf(g, "%s: ARexx is inaccessible\n", PROGNAME);
@.ARexx is inaccesible@>
  }
  FreeArgs(args);
}


@* \[3] Scanning the directory hierarchy.
A utility for finding files is not terribly useful if it cannot search
directories. The function |scan_dirs| takes care of that by descending
the directory hierarchy, matching each directory entry with the
|exec_expr| function. We do the scanning with the \.{DOS} function
|ExAll| for two reasons: (1)~It is faster than using |Examine| and
|ExNext| and (2)~if something is deleted in a directory being scanned
with |ExNext|, {\it horrible\/} things may happen. An early prototype
version of this program actually used |ExNext|, and everything worked
all right until the following was tried:
$$
\.{find ram: name \#?.bak exec "delete \{\}"}
$$
As soon as the first file was deleted, the system crashed. If you read
the description, in ``The AmigaDOS Manual'', of how the packet used by
|ExNext| is supposed to work, it is hardly surprising that
implementations of it can be buggy. The |ExAll| function stores
information about several directory entries as partial |ExAllData|
structures in the buffer |EAData|. We allocate |DIRBUFSIZE| bytes for
|EAData|. Needless to say, the buffer must be big enough to hold the
information about at least one entry.

@d DIRBUFSIZE 2048L

@c
long scan_dirs(uchar *dir, uchar *path, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  struct ExAllData *ead;
  struct ExAllControl *eac;
  uchar *EAData;
  uchar *current_name;
  bcpl_ptr lock, oldlock;
  long rc = RETURN_OK;

  if (!(current_name = AllocVec(strlen(path)+108+2, MEMF_ANY)))
    rc = ERROR_NO_FREE_STORE;
  else {
    if (!(lock = Lock(dir, SHARED_LOCK))) rc = RETURN_FAIL;
    else {
      oldlock = CurrentDir(lock);

      if (!(EAData = AllocVec(DIRBUFSIZE, MEMF_ANY)))
	rc = ERROR_NO_FREE_STORE;
      else {
	if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
	  rc = ERROR_NO_FREE_STORE;
	else {
          @<Scan directories using the |ExAll| function@>@;@/
	  FreeDosObject(DOS_EXALLCONTROL, eac);
	}
	FreeVec(EAData);
      }
      CurrentDir(oldlock);
      UnLock(lock);
    }
    FreeVec(current_name);
  }
  return rc;
}

@ Except for the contents of the innermost loop, this code---that does
the actual scanning of a directory---was copied almost verbatim from
the |ExAll| example in the ``Amiga ROM Kernel Reference Manual:
Includes and Autodocs''.

@<Scan dir...@>=
{
  bool more;

  eac->eac_LastKey = 0;
  do
  {
    more = ExAll(lock, (struct ExAllData *) EAData,
                 DIRBUFSIZE, ED_COMMENT, eac);
    if (!more && (IoErr() != ERROR_NO_MORE_ENTRIES)) {
      rc = RETURN_FAIL;
      break; /* failed abnormally */
    }
    if (eac->eac_Entries == 0) continue;
    ead = (struct ExAllData *) EAData;
    do
    {
      @<If a break signal (Ctrl-C) has been recieved, |goto stop|@>@;@/
      strcpy(current_name, path);
      AddPart(current_name, ead->ed_Name, strlen(current_name) + 108 + 2);
      if (ead->ed_Type < 0 || ead->ed_Type == ST_SOFTLINK)
        @<Match file or soft link@>@;@/
      else
        @<Match directory@>@;@/
      if (g->prune) g->prune = FALSE;
      if (g->NoRexx) rc = RETURN_FAIL;
      if (rc != RETURN_OK) goto stop;

      ead = ead->ed_Next;
    } while (ead);
  } while (more);
}
stop:

@ @<If a break...@>=
if (CheckSignal(SIGBREAKF_CTRL_C)) {
  rc = ERROR_BREAK;
  goto stop;
}

@ @<Match file...@>=
exec_expr(g->first_node, current_name, ead, g);

@ The way directories are matched is determined by the |g->depth|
flag. If |g->depth==TRUE| we enter the directory recursively {\it
before\/} we match it with the expression. If |g->depth==FALSE|
the matching is done first and the directory is entered {\it
afterwards\/} only if the |g->prune| flag has not been set by the
call to |exec_expr|. This is the default behaviour, but it causes a
problem if the directory is deleted as a side-effect of the matching.
The solution is to use the \.{PRUNE} primary together with the primary
that deletes the directory; i.e, using something like
$$\.{find foo: type dir name bar exec "delete \{\} all" prune}$$
instead of
$$\.{find foo: type dir name bar exec "delete \{\} all"}$$
In this version of \FIND, an error is reported if the directory has
disappeared. Maybe it will be a better idea to set the |prune| flag
automagically in that case instead of troubling the user.

@<Match dir...@>=
{
  if (g->depth) {
    rc = scan_dirs(ead->ed_Name, current_name, g);
    exec_expr(g->first_node, current_name, ead, g);
  }
  else {
    exec_expr(g->first_node, current_name, ead, g);
    if (!g->prune)
      rc = scan_dirs(ead->ed_Name, current_name, g);
  }
}

@* \[4] Compiling the expression.
The expression is made up of one or more boolean
``functions''---called ``primaries'' in the manual page for
\UNIX~\FIND, a terminology we will adobt---and the operators \.{NOT}
and \.{OR} (The juxtaposition of two primaries implies an \.{AND}
operator). Braces can be used to make compound sub-expressions. The
primaries fall in two basic categories: Those that test some criterion
and those that have side-effects, like executing another program or
printing.  The evaluation, handled by the expression executing code,
which will be presented later, is left to right and is
``short-circuited''; i.e, the right-hand operand of \.{AND} or \.{OR}
is evaluated only if the outcome cannot be determined by the left-hand
one. This is a very important feature because apart from being an
optimization, it allows conditional execution of primaries. Thus the
expression
$$\.{name \#?.c print}$$
corresponds to the following pseudo-code:
$$\.{IF \#?.c matches the current name THEN print the name}$$
With a little thought, very elaborate expressions can be created.

@ The expression is parsed into a tree consisting of |ExprNode|s. They
are used to hold both operators and primaries together with their
``arguments''. As a given primary will use only a few---if any---of
the members intended for parameters, we could save some storage by
making clever use of |union|s; on the other hand, doing that will
complicate the code that handles the primaries.

@<Global |typ...@>=
typedef struct exprnode {
  tok_type token;
  tok_type comparison;
  long number;
  long setmask;
  long unsetmask;
  uchar *string;
  struct exprnode *left;
  struct exprnode *right;
} ExprNode;

@ This function is the entrypoint of the expression compiler. It
returns a pointer to the start of the expression tree or |NULL| if an
error occured. |*rc| points to the return status code. Its true
purpose is to hide the fact that the compilation is aborted with a
|longjmp| in case of an error.

@c
ExprNode *expr_compiler(uchar *expr_line, long *rc, Global *g)
{
  int ret;

  ret = setjmp(g->jmp_save);
  if (ret) {
    *rc = ret;
    return NULL;
  }
  else {
    *rc = RETURN_OK;
    return compile_expr(expr_line, g);
  }
}


@ This function builds the expression tree. The parsing is done from
left to right, occasionally looking ahead one token.

@c
ExprNode *compile_expr(uchar *expr_line, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  ExprNode *temp_operator, *head_operator = NULL, *primary;
  uchar *pos, *oldpos;
  tok_type tok = TOK_UNKNOWN;

  oldpos = pos = expr_line;
  for (pos = get_token(&tok, pos, g); tok != TOK_EOS;
       pos = get_token(&tok, pos, g)) {
    primary = alloc(sizeof(ExprNode), g);
    switch (tok) {
    case TOK_UNKNOWN:
      MyPrintf(g, "%s: Unknown identifier\n", PROGNAME);
@.Unknown identifier@>
      SetIoErr(ERROR_LINE_TOO_LONG);
      longjmp(g->jmp_save, RETURN_FAIL);
      break;
    case TOK_NOT:
      @<Handle the \.{NOT} operator@>;
      break;
    case TOK_OR:
      @<Handle the \.{OR} operator@>;
      break;
    case TOK_LB:
      @<Handle sub-expression surrounded by braces@>;
      break;
    default:
      pos = compile_prim(primary, oldpos, g);
    }
    @<Attach |primary| to the expression tree@>;
    oldpos = pos;
  }
  return head_operator;
}

@ This piece of code handles the prefix \.{NOT} operator. The
negation of a ``compound'' sub-expression, i.e., one surrounded by
braces is treated as a special case.

@<Handle the \.{NOT}...@>=
temp_operator = alloc(sizeof(ExprNode), g);
temp_operator->token = TOK_NOT;
get_token(&tok, pos, g); /* look at the next token */
if (tok == TOK_LB) {
  pos = handle_braces(&primary, ++pos, g);
  temp_operator->right = primary;
  if (!primary) continue;
}
else {
  pos = compile_prim(primary, pos, g);
  temp_operator->right = primary;
}
primary = temp_operator;

@ The \.{OR} keyword is an infix operator. If |head_operator==NULL|,
the left hand operand is missing and we report a syntax error. In that
case the \.{DOS} secondary result is set to |ERROR_LINE_TOO_LONG|.
This may seem like a strange choice, but the message it produces is
the best \.{DOS} has to offer.

@<Handle the \.{OR}...@>=
if (!head_operator) {
  MyPrintf(g, "%s: Misplaced OR\n", PROGNAME);
@.Misplaced OR@>
  SetIoErr(ERROR_LINE_TOO_LONG);
  longjmp(g->jmp_save, RETURN_FAIL);
}
temp_operator = alloc(sizeof(ExprNode), g);
temp_operator->token = TOK_OR;
temp_operator->left = head_operator;
if (temp_operator->right = compile_expr(pos, g))
  return temp_operator;
else
  return head_operator;

@ @<Handle sub-expr...@>=
pos = handle_braces(&primary, pos, g);
if (!primary) continue;

@ @<Attach |prim...@>=
if (!head_operator) head_operator = primary;
else {
  temp_operator = alloc(sizeof(ExprNode), g);
  temp_operator->token = TOK_AND;
  temp_operator->left = head_operator;
  temp_operator->right = primary;
  head_operator = temp_operator;
}

@ When this function is entered, |pos| points to the left brace. We
then scan through the string, looking for the matching right brace.
The expression inside the braces is then parsed by calling
|compile_expr| recursively. If the braces are not balanced, we report
an error and exit the expression compiler.

@c
uchar *handle_braces(ExprNode **primary, uchar *pos, Global *g)
{
  uchar *pos2;
  long braces;

  pos2 = pos;
  for (braces = 0; *pos && (*pos != RBRACE || braces); pos++) {
    if (*pos == LBRACE) braces++;
    if (*pos == RBRACE) braces--;
  }

  if (braces) {
    MyPrintf(g, "%s: Unmatched brace\n", PROGNAME);
@.Unmatched brace@>
    longjmp(g->jmp_save, RETURN_FAIL);
  }

  *pos++ = '\0'; /* replace the closing brace */
  *primary = compile_expr(pos2, g);
  return pos;
}

@ This function is called by |compile_expr| to fill in the |ExprNode|s
for primaries. It is basically just a big |switch|, the details of
which will be filled out as soon as we have shown the outline of the
function that executes the compiled expression. As the \.{PRINT} and
\.{PRUNE} primaries do not take any arguments, storing their tokens is
all we need to do.

@c
uchar *compile_prim(ExprNode *primary, uchar *pos, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  tok_type tok, lookahead;
  uchar *newpos;
  long  len;
  struct DateTime dt;

  pos = get_token(&tok, pos, g);
  if (tok != TOK_EOS) {
    primary->token = tok; /* This is done for all primaries */
    switch (tok)
    {
    case TOK_NAME:
    case TOK_COMMENT: @<Compile pattern argument@>@; break;
    case TOK_LFORMAT:
    case TOK_EXEC:
    case TOK_EXECOK:
    case TOK_REXX: @<Compile string argument@>@; break;
    case TOK_SIZE:
    case TOK_DAYS:
      @<Compile numerical argument with optional comparison operator@>@;
      break;
    case TOK_SINCE:
    case TOK_UPTO: @<Compile date@>@; break;
    case TOK_TYPE: @<Compile object type@>@; break;
    case TOK_FLAGS: @<Compile protection flags@>@; break;
    case TOK_PRINT:
    case TOK_PRUNE: break;
    default:
      MyPrintf(g, "%s: Unknown or misplaced identifier\n", PROGNAME);
@.Unknown or misplaced identifier@>
      SetIoErr(ERROR_LINE_TOO_LONG);
      longjmp(g->jmp_save, RETURN_FAIL);
      break;
    }
  }
  return pos;
}

@* \[5] Executing the compiled expression.
Now that we have built a tree representation of the expression, we
need a way to execute it. Note how easy it is to do the
short-circuiting when traversing the tree.

@c
bool exec_expr(ExprNode *tree_node, uchar *rel_path,
		 struct ExAllData *ead, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  long days;
  long flags;
  bool res = FALSE;

  switch (tree_node->token)
  {
  case TOK_OR:
    if (exec_expr(tree_node->left, rel_path, ead, g))
      res = TRUE;
    else
      res = exec_expr(tree_node->right, rel_path, ead, g);
    break;

  case TOK_AND:
    if (exec_expr(tree_node->left, rel_path, ead, g))
      res = exec_expr(tree_node->right, rel_path, ead, g);
    else
      res = FALSE;
    break;

  case TOK_NOT:
    res = !exec_expr(tree_node->right, rel_path, ead, g);
    break;

  case TOK_NAME: @<Match name@>@; break;
  case TOK_COMMENT: @<Match comment@>@; break;
  case TOK_PRINT: @<Print name@>@; break;
  case TOK_PRUNE: g->prune = TRUE; res = TRUE; break;
  case TOK_SIZE: @<Compare size@>@; break;
  case TOK_DAYS: @<Check age@>@; break;
  case TOK_TYPE: @<Does the type match@>@; break;
  case TOK_FLAGS: @<Match the protection flags@>@; break;
  case TOK_SINCE: res = ead->ed_Days >= tree_node->number; break;
  case TOK_UPTO: res = ead->ed_Days <= tree_node->number; break;
  case TOK_LFORMAT:
    res = lformat(rel_path, tree_node->string, ead, g);
    break;
  case TOK_EXEC: 
    res = exec_cmd(rel_path, tree_node->string, FALSE, g);
    break;
  case TOK_EXECOK:
    res = exec_cmd(rel_path, tree_node->string, TRUE, g);
    break;
  case TOK_REXX:
    res = exec_Rexx(rel_path, tree_node->string, ead, g);
    break;
  }
  return res;
}

@* \[6] Handling the expression primaries.
It's time now to reveal the details of how the primaries are compiled
and executed. Note that we call the |ExprNode| |primary| when
compiling, but |tree_node| when executing.

@ This code handles the primaries \.{NAME} and \.{COMMENT}, which take
a pattern as their argument. The pattern in |g->scratch| is
tokenized by calling |ParsePatternNoCase|. The tokenized pattern is
stored in the \.{string} member of the |ExprNode|.  The error message
issued if it cannot be tokenized is not very informative.

@<Compile pat...@>=
pos = read_item(g->scratch, BUFFERLEN, pos, TERM);
if (!(len = strlen(g->scratch))) {
  SetIoErr(ERROR_KEY_NEEDS_ARG);
  longjmp(g->jmp_save, RETURN_FAIL);
}
len = (len + 1) * 2;
primary->string = alloc(len, g);
if (ParsePatternNoCase(g->scratch, primary->string, len) < 0) {
  SetIoErr(ERROR_LINE_TOO_LONG);
  longjmp(g->jmp_save, RETURN_FAIL);
}

@ The code needed to match names and comments when executing the
expression is very simple. Just a call to the \.{DOS} pattern matching
function.

@<Match nam...@>=
if (MatchPatternNoCase(tree_node->string, ead->ed_Name)) res = TRUE;

@ We only attempt to match a comment if one is present. If there is no
attached comment, the \.{COMMENT} primary fails.

@<Match com...@>=
if (ead->ed_Comment)
  if (MatchPatternNoCase(tree_node->string, &ead->ed_Comment[CMT]))
    res = TRUE;

@ The \.{SIZE} and \.{DAYS} primaries expect a numerical argument. To
complicate matters, the argument may be preceded by one of the
following comparison operators: \.{GT}, \.{LT}, or \.{EQ}. We
therefore need to look at the next token to determine if one of said
operators is present. If that is the case, we store it and update
|pos|; if it isn't, we assume the user means \.{EQ}.

@<Compile num...@>=
newpos = get_token(&lookahead, pos, g);
if (lookahead == TOK_GT || lookahead == TOK_LT || lookahead == TOK_EQ) {
  pos = newpos;
  primary->comparison = lookahead;
}
else 
  primary->comparison = TOK_EQ;
pos = read_item(g->scratch, BUFFERLEN, pos, TERM);
if (!strlen(g->scratch)) {
  SetIoErr(ERROR_KEY_NEEDS_ARG);
  longjmp(g->jmp_save, RETURN_FAIL);
}
primary->number = tonumber(g->scratch, g);

@ When comparing the size in bytes of the entry, we have to treat
directories as a special case as size information is not applicable to
them.

@<Compare siz...@>=
if (ead->ed_Type >= 0) res = FALSE;
else {
  switch (tree_node->comparison) {
    case TOK_GT: res = ead->ed_Size > tree_node->number; break;
    case TOK_LT: res = ead->ed_Size < tree_node->number; break;
    case TOK_EQ: res = ead->ed_Size == tree_node->number;
  }
}

@ The code for checking the age of a file in days, is almost the same
as the above code for checking the size.

@<Check age@>=
days = g->days - ead->ed_Days;
switch (tree_node->comparison) {
  case TOK_GT: res = days > tree_node->number; break;
  case TOK_LT: res = days < tree_node->number; break;
  case TOK_EQ: res = days == tree_node->number;
}

@ The \.{TYPE} primary's argument is one of the type identifiers
known by |get_token|, so we handle it by storing the corresponding
token.

@<Compile obj...@>=
pos = get_token(&lookahead, pos, g);
switch (lookahead) {
case TOK_FILE:
case TOK_LFILE:
case TOK_DIR:
case TOK_LDIR:
case TOK_SOFT: primary->number = (long) lookahead; break;
case TOK_EOS:
  SetIoErr(ERROR_KEY_NEEDS_ARG);
  longjmp(g->jmp_save, RETURN_FAIL);
default:
  MyPrintf(g, "%s: Unknown type\n", PROGNAME);
@.Unknown type@>
  longjmp(g->jmp_save, RETURN_FAIL);
}

@ The code for matching the type, when executing, is trivial. Note
that there are two directory types in the standard Amiga file systems:
|ST_ROOT| and |ST_USERDIR|.

@<Does the type...@>=
switch ((tok_type) tree_node->number) {
case TOK_FILE: res = ead->ed_Type == ST_FILE; break;
case TOK_LFILE: res = ead->ed_Type == ST_LINKFILE; break;
case TOK_DIR: res = ead->ed_Type == ST_ROOT || ead->ed_Type == ST_USERDIR;
              break;
case TOK_LDIR: res = ead->ed_Type == ST_LINKDIR; break;
case TOK_SOFT: res = ead->ed_Type == ST_SOFTLINK; break;
}

@ The \.{FLAGS} primary's argument, a string of protection flags, is
converted to two bitmasks, one specifying the bits that must be set
and one specifying the ones that must be unset in order for a file to
match. If a trailing dash is present in the string, it is silently
ignored.

@<Compile pro...@>=
pos = read_item(g->scratch, BUFFERLEN, pos, TERM);
if (!strlen(g->scratch)) {
  SetIoErr(ERROR_KEY_NEEDS_ARG);
  longjmp(g->jmp_save, RETURN_FAIL);
}
primary->setmask = primary->unsetmask = 0L;
for (newpos = g->scratch; *newpos; newpos++) {
  if (*newpos == '-') {
    if (*++newpos) set_bit(*newpos, &primary->unsetmask, g);
  }
  else set_bit(*newpos, &primary->setmask, g);
}
if (primary->setmask & primary->unsetmask) {
  MyPrintf(g, "%s: Ambiguous flag specification\n", PROGNAME);
@.Ambiguous flag specification@>
  longjmp(g->jmp_save, RETURN_FAIL);
}

@ This function is called by the code in the previous section, as it
scans through the string of protection flags. The first statement
ought to be |c=tolower(c);|.

@c
void set_bit(uchar c, long *mask, Global *g)
{
  c |= 0x20;
  switch (c)
  {
  case 's': *mask |= FIBF_SCRIPT;  break;
  case 'p': *mask |= FIBF_PURE;    break;
  case 'a': *mask |= FIBF_ARCHIVE; break;
  case 'r': *mask |= FIBF_READ;    break;
  case 'w': *mask |= FIBF_WRITE;   break;
  case 'e': *mask |= FIBF_EXECUTE; break;
  case 'd': *mask |= FIBF_DELETE;  break;
  default:
    MyPrintf(g, "%s: Unknown protection flag\n", PROGNAME);
@.Unknown protection flag@>
    longjmp(g->jmp_save, RETURN_FAIL);
  }
}

@ All we need to do when matching the flags is to flip the lower 4
bits and get rid of the higher 24 of the file's protection flags (they
are not used by the system). The resulting |flags| are then matched
with the bitmasks created when \.{FLAGS} was compiled.

@<Match the prot...@>=
flags = (~ead->ed_Prot & 0x0000000f) | (ead->ed_Prot & 0x000000f0);
res = (flags & tree_node->setmask) == tree_node->setmask &&
	(flags & tree_node->unsetmask) == 0L;

@ When \.{PRINT} is executed, it simply prints |rel_path| on the
default output.

@<Print name@>= MyPrintf(g, "%s\n", rel_path); res = TRUE;

@ \.{SINCE} and \.{UPTO} takes a date argument that must be a valid
\ADOS\ date string. It is converted to a |DateTime| structure, and
the |dat_Stamp.ds_Days| member (which is a |long|) is stored in the
\.{number} member of the |ExprNode|.

@<Compile dat...@>=
pos = read_item(g->scratch, BUFFERLEN, pos, TERM);
if (!strlen(g->scratch)) {
  SetIoErr(ERROR_KEY_NEEDS_ARG);
  longjmp(g->jmp_save, RETURN_FAIL);
}
dt.dat_Format = FORMAT_DOS;
dt.dat_Flags = 0;
dt.dat_StrDate = g->scratch;
dt.dat_StrTime = NULL;
if (!StrToDate(&dt)) {
  MyPrintf(g, "%s: Bogus date\n", PROGNAME);
@.Bogus date@>
  longjmp(g->jmp_save, RETURN_FAIL);
}
primary->number = dt.dat_Stamp.ds_Days;

@ When compiling the \.{LFORMAT}, \.{EXEC}, \.{EXECOK}, and \.{REXX}
primaries, we simply store a copy of the argument string in the
|ExprNode|. They are handled by special functions when the expression
is executed.

@<Compile string...@>=
pos = read_item(g->scratch, BUFFERLEN, pos, TERM);
if (!strlen(g->scratch)) {
  SetIoErr(ERROR_KEY_NEEDS_ARG);
  longjmp(g->jmp_save, RETURN_FAIL);
}
primary->string = dupstr(g->scratch, g);

@ The following function is called when the \.{LFORMAT} primary is
executed. The |fmt| argument points to a string consisting of ordinary
characters and conversion specifications. Each conversion is replaced
by the results of the conversion before it is copied to the default
output. A conversion specification begins with a percent character. To
output a percent character, it is necessary to precede it with another
percent character. A single percent character at the end of the |fmt|
string looses its special meaning and is copied verbatim. The
specification \%N prints the name of the object, \%R prints it with
its relative path, \%L prints the length in bytes (this is only useful
for file type objects), and \%C prints any attached comments.

@c
bool lformat(uchar *rpath, uchar *fmt, struct ExAllData *ead,Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  bcpl_ptr out = Output();

  for (; *fmt; fmt++)
    if (*fmt == '%')
      switch (*++fmt)
      {
        case 'n': case 'N': FPuts(out, ead->ed_Name); break;
        case 'r': case 'R': FPuts(out, rpath); break;
        case 'l': case 'L': MyPrintf(g, "%ld", ead->ed_Size); break;
        case 'c': case 'C':
          if (ead->ed_Comment)
            FPuts(out, &ead->ed_Comment[CMT]);
          break;
        case '\0': FPutC(out, '%'); break;
        default: FPutC(out, *fmt);
      }
    else
      FPutC(out, *fmt);
  FPutC(out, '\n');
  Flush(out);
  return TRUE;
}

@ This function executes an external command. A command argument |{}|
is replaced by the current filename, |file|. Before the command is
executed, we temporarily \.{CD} to the directory \FIND\ was launched
from. This is necessary because |file| contains a pathname relative
to that directory. If |prompt==TRUE| the user is asked to confirm that
the command should be executed. If the answer is no, |exec_cmd| fails.

@c
bool exec_cmd(uchar *file, uchar *cmd_str, bool prompt, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  uchar *p, *fp;
  long len = BUFFERLEN, ret;
  bcpl_ptr oldlock;
  struct TagItem systags[] = { {SYS_UserShell, TRUE}, {TAG_DONE} };

  @<Copy |cmd_str| to |g->scratch|, replacing occurences of |{}|@>@;@/
  @<Prompt the user if |prompt==TRUE|@>@;@/
  oldlock = CurrentDir(g->cdlock);
  ret = SystemTagList(g->scratch, systags);
  CurrentDir(oldlock);
  if (ret == 0) return TRUE;
  return FALSE;
}

@ The code that substitutes |file| for each occurence of |{}| in
|cmd_str| is fairly straightforward. The resulting string is at most
|len| characters long.

@<Copy |cmd...@>=
for (p = g->scratch; --len && *cmd_str; cmd_str++) {
  if (*cmd_str == LBRACE && *(cmd_str+1) == RBRACE) {
    for (fp = file; --len && *fp; fp++)
      *p++ = *fp;
    cmd_str++; /* move past the right brace */
  }
  else
    *p++ = *cmd_str;
}
*p = '\0';

@ Prompt the user. If he or she does not answer in the affirmative, we
|return FALSE|. If we did not call |Flush|, the prompt string would
appear after the user had responded.

@<Prompt...@>=
if (prompt) {
  bcpl_ptr in = Input();
  long c;

  MyPrintf(g, "%s ? ", g->scratch);
  Flush(Output());
  do
  {  
    c = FGetC(in);
    if (c == 'n' || c == 'N') return FALSE;
  } while (c != 'y' && c != 'Y');
  while ((c = FGetC(in)) != '\n')
    ;
}

@ This function acts as an interface to the intuition.library function
|AllocRemember|. If it cannot allocate the requested memory, it
|longjmp|s out of the compiler.

@c
address_ptr alloc(ulong size, Global *g)
{
  struct Library *IntuitionBase = g->IntuitionBase;
  address_ptr mem;

  mem = AllocRemember(&g->RememberKey, size, MEMF_ANY | MEMF_CLEAR);
  if (!mem) longjmp(g->jmp_save, ERROR_NO_FREE_STORE);
  return mem;
}

@ This function makes a copy of the string pointed to by |s|.

@c
uchar *dupstr(uchar *s, Global *g)
{
  uchar *to;

  to = alloc(strlen(s)+1, g);
  strcpy(to, s);
  return to;
}

@ This function converts the string |num_str| to a |long| by calling
the \.{DOS} function |StrToLong|. It |longjmp|s out of the compiler on
bogus input.

@c
long tonumber(uchar *num_str, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  long n;

  if (StrToLong(num_str, &n) == -1)
  {
    MyPrintf(g, "%s: number required\n", PROGNAME);
@.number required@>
    longjmp(g->jmp_save, RETURN_FAIL);
  }
  return n;
}

@* \[7] ARexx interface.
The following function executes the ARexx ``string file'' given as the
argument to \.{REXX} primary.

@c
bool exec_Rexx(uchar *rel_path, uchar *cmd_str,
	      struct ExAllData *ead, Global *g)
{
  struct Library *RexxSysBase = g->RexxSysBase;
  struct MsgPort *port, *rxport;
  struct RexxMsg *msg, *rxmsg;
  uchar portname[32], *s;
  long clinum;
  bool ret = TRUE;

  @<Test whether the ARexx library is open; fail if not@>@;@/
  @<Construct |portname| for communicating with ARexx@>@;@/
  @<Create the |port| and make it public@>@;@/
  if (!port) @<Tell |scan_dirs| that we cannot access Arexx@>@;
  else {
    if (!(msg = CreateRexxMsg(port, NULL, port->mp_Node.ln_Name)))
      @<Tell...@>@;@/
    else {
      msg->rm_Action = RXCOMM | RXFF_STRING;
      msg->rm_Args[0] = cmd_str;
      if (!FillRexxMsg(msg, 1, 0)) @<Tell...@>@;@/
      else {
	@<Try to send |msg| to ARexx' message port, |rxport|@>@;@/
	if (!rxport) @<Tell...@>@;@/
	else @<Process |rxmsg|es arriving at |port|@>@;@/
	ClearRexxMsg(msg, 1);
      }
      DeleteRexxMsg(msg);
    }
    RemPort(port);
    DeleteMsgPort(port);
  }
  return ret;
}

@ This is the correct place to bother the user with the fact that
rexxsyslib.library could not be opened.

@<Test whether the ARexx...@>=
if (!RexxSysBase) @<Tell...@>@;

@ The port name is |PORTNAME| with the cli task number of \FIND\
appended.

@<Construct...@>=
strcpy(portname, PORTNAME);
s = &portname[strlen(portname)];
clinum = ((struct Process *) FindTask(NULL))->pr_TaskNum;
long_to_str(s, clinum);

@ We forbid task switching while we create the message port to avoid
any possibility of a race condition. We set its priority field to |1|
to put it closer to the beginning of the list of public message ports.

@<Create the |port...@>=
Forbid();
if (!FindPort(portname)) {
  if (port = CreateMsgPort()) {
    port->mp_Node.ln_Pri = 1L;
    port->mp_Node.ln_Name = portname;
    AddPort(port);
  }
}
Permit();

@ We get the address of ARexx' public port by calling |FindPort|. As a
safeguard against the possibility that it might disappear before we
get a chance to send our message, we forbid task switching.

@<Try to send...@>=
Forbid();
if (rxport = FindPort("REXX")) PutMsg(rxport, (struct Message *) msg);
Permit();

@ This is the double loop that handles the messages arriving at
|port|. When the reply (a message with type |NT_REPLYMSG|) arrives, we
set |ret=TRUE| if Arexx returned with a zero exit code (|FALSE|
otherwise) and break out of the loops. All other messages are commands
to be processed by |handle_msg|. Maybe a use for the optional
result string ARexx may request, will be found in the future; e.g.,
for the full pathname.

@<Process |rxmsg...@>=
loop@+{
  WaitPort(port);
  while (rxmsg = (struct RexxMsg *) GetMsg(port)) {
    if(rxmsg->rm_Node.mn_Node.ln_Type == NT_REPLYMSG) {
      ret = rxmsg->rm_Result1 ? FALSE : TRUE;
      ReplyMsg((struct Message *) rxmsg);
      goto exit_loops;
    } 
    else {
      rxmsg->rm_Result1 =
         handle_msg(rxmsg, rxmsg->rm_Args[0], rel_path, ead, g);
      rxmsg->rm_Result2 = (long) NULL;
      if (rxmsg->rm_Action & RXFF_RESULT) {
        if (s = CreateArgstring("RESULT NOT SUPPORTED!", 21L))
@.RESULT NOT SUPPORTED!@>
          rxmsg->rm_Result2 = (long) s;
        else
          rxmsg->rm_Result1 = 20;
      }
    }
    ReplyMsg((struct Message *) rxmsg);
  }
}
exit_loops:

@ @<Tell...@>=
{
  g->NoRexx = TRUE;
  ret = FALSE;
}

@ This function handles the only command, |GETATTR|, supported by
\FIND's ARexx interface. When the user supplied stem name has been
copied to |stem|, |s| is left pointing to the address following its
last character. Thus we can repeatedly concatenate the node names to
the stem name with |strcpy|, overwriting the previous node name.

@c
long handle_msg(struct RexxMsg *rxmsg, uchar *cmd, 		
		  uchar *rpath, struct ExAllData *ead, Global *g)
{
  struct DosLibrary *DOSBase = g->DOSBase;
  struct Library *UtilityBase = g->UtilityBase;
  uchar *s, stem[65];
  long size = 64;

  @<Store command name in |stem|@>@;@/
  if (Stricmp(stem, GETATTR) == 0) {
    @<Read stem variable name into |stem|@>@;@/
    @<Set |FULLNAME| node or fail@>@;@/
    @<Set |RELNAME| node or fail@>@;@/
    @<Set |NAME| node or fail@>@;@/
    @<Set |COMMENT| node or fail@>@;@/
    @<Set |TYPE| node or fail@>@;@/
    @<Set |SIZE| node or fail@>@;@/
    @<Set |PROT| node or fail@>@;@/
    @<Set |DAY|, |DATE|, and |TIME| nodes or fail@>@;@/
    @<Set |DAYS|, |MINS|, and |TICKS| nodes or fail@>@;@/
    return 0;
  }
  return 20;
}

@ This code extracts the name of the command from |cmd| and stores it
in |stem|. This code could (and probably should) easily be replaced by
a call to |read_item|.

@<Store command...@>=
cmd = skip_whitespace(cmd);
for (s = stem; size-- && *cmd && *cmd != ' ' && *cmd != '\t'; cmd++)
  *s++ = *cmd;
*s = '\0';

@ Copy the name of the stem variable into |stem|. When done, |s|
points to the end of the name.

@<Read stem var...@>=
cmd = skip_whitespace(cmd);
for (s = stem; size-- && *cmd && *cmd != ' ' && *cmd != '\t'; cmd++)
  *s++ = *cmd;

@ @<Set |FULLNAME...@>=
@<Obtain the full pathname of the current file system object@>@;@/
strcpy(s, "FULLNAME");
if (SetRexxVar(rxmsg, stem, g->scratch, strlen(g->scratch)))
  return 30;

@ @<Set |RELNAME...@>=
strcpy(s, "RELNAME");
if (SetRexxVar(rxmsg, stem, rpath, strlen(rpath))) return 30;

@ @<Set |NAME...@>=
strcpy(s, "NAME");
if (SetRexxVar(rxmsg, stem, ead->ed_Name, strlen(ead->ed_Name))) return 30;

@ @<Set |COMMENT...@>=
strcpy(s, "COMMENT");
if (ead->ed_Comment) {
  if (SetRexxVar(rxmsg, stem, &ead->ed_Comment[CMT],
                 strlen(&ead->ed_Comment[CMT]))) return 30;
}
else {
  if (SetRexxVar(rxmsg, stem, "", strlen(""))) return 30;
}

@ @<Set |TYPE...@>=
{
uchar type_name[12];

strcpy(s, "TYPE");
switch (ead->ed_Type) {
case ST_FILE:     strcpy(type_name, "FILE");     break;
case ST_LINKFILE: strcpy(type_name, "LINKFILE"); break;
case ST_ROOT:
case ST_USERDIR:  strcpy(type_name, "DIR");      break;
case ST_LINKDIR:  strcpy(type_name, "LINKDIR");  break;
case ST_SOFTLINK: strcpy(type_name, "SOFTLINK"); break;
default:          strcpy(type_name, "UNKNOWN");
}
if (SetRexxVar(rxmsg, stem, type_name, strlen(type_name))) return 30;
}

@ @<Set |SIZE...@>=
if (setRexxNumVar(rxmsg, stem, s, "SIZE",
                  ead->ed_Type < 0 ? ead->ed_Size : 0L)) return 30;

@ @<Set |PROT...@>=
if (setRexxNumVar(rxmsg, stem, s, "PROT", ead->ed_Prot)) return 30;

@ @<Set |DAYS|,...@>=
if (setRexxNumVar(rxmsg, stem, s, "DAYS", ead->ed_Days)) return 30;
if (setRexxNumVar(rxmsg, stem, s, "MINS", ead->ed_Mins)) return 30;
if (setRexxNumVar(rxmsg, stem, s, "TICKS", ead->ed_Ticks)) return 30;

@ @<Set |DAY|,...@>=
{
  struct DateTime dt;
  uchar day[LEN_DATSTRING], date[LEN_DATSTRING], time[LEN_DATSTRING];
    
  @<Fill in the |DateTime| structure, |dt|@>@;@/
  DateToStr(&dt);@/
  strcpy(s, "DAY");
  if (SetRexxVar(rxmsg, stem, day, strlen(day))) return 30;
  strcpy(s, "DATE");
  if (SetRexxVar(rxmsg, stem, date, strlen(date))) return 30;
  strcpy(s, "TIME");
  if (SetRexxVar(rxmsg, stem, time, strlen(time))) return 30;
}

@ @<Fill...@>=
dt.dat_Stamp.ds_Days = ead->ed_Days;@/
dt.dat_Stamp.ds_Minute = ead->ed_Mins;@/
dt.dat_Stamp.ds_Tick = ead->ed_Ticks;@/
dt.dat_Format  = FORMAT_DOS;@/
dt.dat_Flags   = 0;@/
dt.dat_StrDay  = day;@/
dt.dat_StrDate = date;@/
dt.dat_StrTime = time;

@ @<Obtain the full path...@>=
{
  bcpl_ptr lock = Lock(ead->ed_Name, SHARED_LOCK);
  NameFromLock(lock, g->scratch, BUFFERLEN-1);
  UnLock(lock);
}

@ This function sets an ARexx variable to the numerical value |n|.

@c
long setRexxNumVar(struct RexxMsg *rxmsg,
		   uchar *stem, uchar *end, uchar *var, long n)
{
  uchar num_str[12];

  strcpy(end, var);
  long_to_str(num_str, n);
  if (SetRexxVar(rxmsg, stem, num_str, strlen(num_str))) return 30;
  return 0;
}

@ This function converts a |long| to a character string. The
12~elements of |temp| is enough to hold all the digits and the sign of
a 32~bit |long|.

@c
uchar *long_to_str(uchar *s, long n)
{
  long len;
  uchar temp[12], *s1, *s2 = s;

  if (n < 0) {
    n = -n;
    *s2++ = '-';
  }

  s1 = temp;
  len = 0;
  do
  {
    *s1++ = '0' + n % 10;
    len++;
  } while (n /= 10);

  while (len--)
    *s2++ = *--s1;
  *s2 = '\0';

  return s;
}

@* \[8] Lexical scanner.
\FIND's lexical scanner consists of several functions. At the highest
level. we find the function |get_token|, which gets the next |token|
from |pos|. We do a simple linear search of the lookup. There is no
need for anything fancier than that, because the expression is only
compiled once, and the time it takes to do that, even for very
elaborate expressions, is negligible compared to the time \FIND\
typically spends scanning the directory hierarchy. A quick and dirty
kludge has been added here, to make \FIND\ accept the keyword \.{AND}
in an expression. If it is encountered, we simply |goto try_again| and
get the next |token|, effectively filtering out the \.{AND} before the
parser gets a chance to see it.

@c
uchar *get_token(tok_type *token, uchar *pos, Global *g)
{
  struct Library *UtilityBase = g->UtilityBase;

 try_again:
  pos = skip_whitespace(pos);
  if (!*pos)
    *token = TOK_EOS;
  else {
    *token = TOK_UNKNOWN;

    if ((*pos >= 'a' && *pos <= 'z') || (*pos >= 'A' && *pos <= 'Z')) {
      long i;

      pos = read_item(g->scratch, BUFFERLEN, pos, TERM);
      for (i = 0; i < NTOKS; i++) {
	if (Stricmp(g->scratch, tok_table[i].string) == 0)	{
	  *token = tok_table[i].token;
	  if (*token == TOK_AND) goto try_again;
	  break;
	}
      }
    }
    else {
      switch(*pos++) {
      case LBRACE:  *token = TOK_LB; break;
      case RBRACE:  *token = TOK_RB;
      }
    }
  }
  return pos;
}

@ This function is the heart of the lexical scanner. It works by
copying the first ``item'' in the string |str| to the buffer pointed
to by |buf|. At most |bufsize-1| characters are read. The |termstr| is
a list of characters marking the end of the item. The |termstr|
characters lose their special meaning inside quotes. Quotes are
stripped unless they are escaped. |"*"| is used as the escape
character: |"*n"| is a newline, |"*t"| a tab. To get an |"*"| use
|"**"|. A pointer to the last character copied is returned.
\smallskip\noindent
Example: |read_item(buf, 50L, str, " {}+-");|

@c
uchar *read_item(uchar *buf, long bufsize, uchar *str, uchar *termstr)
{
  bool inquotes = FALSE;

  str = skip_whitespace(str);
  if (*str == '\"') {
    str++;
    inquotes = TRUE;
  }

  for (; --bufsize && *str && !terminator(*str, termstr, inquotes); str++) {
    if (*str == ESCCHAR)
      if (*++str)
	*str = escape(*str);
      else
	break;

    *buf++ = *str;
  }
  *buf = '\0';

  if (*str == '\"')
    str++;

  return str;
}


@ This function moves past leading white-space in |s|. We will call it
from just about everywhere in the program.

@c
uchar *skip_whitespace(uchar *s)
{
  while (*s == ' ' || *s == '\t')
    s++;
  return s;
}

@ Handling the escape character substitutions is easy.  Non-special
characters are simply passed through unchanged.

@c
long escape(long c)
{
  switch (c)
  {
  case 'n': return '\n';
  case 't': return '\t';
  default: return c;
  }
}

@ The |terminator| function checks whether the end of the current
``item'' has been reached. It is called only by |read_item|. Its
behaviour is determined by the |inquotes| flag. If |inquotes==TRUE|,
the function will |return TRUE| for |c=='\"'|.  Otherwise, it will
|return TRUE| if |c| is in |termstr|.

@c
bool terminator(uchar c, uchar *termstr, bool inquotes)
{
  if (inquotes) {
    if (c == '\"')
      return TRUE;
  }
  else
    while (*termstr)
      if (c == *termstr++)
	return TRUE;
  return FALSE;
}

@ This is the token lookup table used by |get_token|.

@d NTOKS (sizeof(tok_table) / sizeof(struct table_el))

@<Look...@>=
struct@+table_el@+{@+uchar *string;@+int token;@+}@+const tok_table[] = {@/
  { "OR",      TOK_OR },@/
  { "AND",     TOK_AND },@/
  { "NOT",     TOK_NOT },@/
  { "NAME",    TOK_NAME },@/
  { "PRINT",   TOK_PRINT },@/
  { "EXEC",    TOK_EXEC },@/
  { "EXECOK",  TOK_EXECOK },@/
  { "SIZE",    TOK_SIZE },@/
  { "DAYS",    TOK_DAYS },@/
  { "SINCE",   TOK_SINCE },@/
  { "UPTO",    TOK_UPTO },@/
  { "TYPE",    TOK_TYPE },@/
  { "PRUNE",   TOK_PRUNE },@/
  { "REXX",    TOK_REXX },@/
  { "FLAGS",   TOK_FLAGS },@/
  { "COMMENT", TOK_COMMENT },@/
  { "LFORMAT", TOK_LFORMAT },@/
  { "GT",      TOK_GT },@/
  { "LT",      TOK_LT },@/
  { "EQ",      TOK_EQ },@/
  { "FILE",    TOK_FILE },@/
  { "LINKFILE",TOK_LFILE },@/
  { "DIR",     TOK_DIR },@/
  { "LINKDIR", TOK_LDIR },@/
  { "SOFTLINK",TOK_SOFT }@/
};

@* \[9] Miscellaneous.
This module contains the most boring parts of the program.

@ We need to |#include| a lot of header files to make \FIND\ work.

@<Global |#inc...@>=
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/dostags.h>
#include <dos/dosasl.h>
#include <dos/exall.h>
#include <dos/datetime.h>
#include <intuition/intuition.h>
#include <rexx/rxslib.h>
#include <rexx/storage.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/utility_protos.h>
#include <clib/intuition_protos.h>
#include <clib/rexxsyslib_protos.h>

#ifdef __SASC
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/rexxsyslib_pragmas.h>
extern long __stdargs SetRexxVar(struct RexxMsg *, uchar *, uchar *, long);
#else
extern long SetRexxVar(struct RexxMsg *, uchar *, uchar *, long);
#endif

#include <string.h>
#include <stdlib.h>
#include <setjmp.h>
#include "tastlib.h"
#include "find_rev.h"

@ This is the function prototypes.

@<Global func...@>=
long scan_dirs(uchar *, uchar *, Global *);@/
uchar *get_token(tok_type *, uchar *, Global *);@/
ExprNode *expr_compiler(uchar *, long *, Global *);@/
uchar *handle_braces(ExprNode **, uchar *, Global *);@/
ExprNode *compile_expr(uchar *, Global *);@/
uchar *compile_prim(ExprNode *, uchar *, Global *);@/
void set_bit(uchar, long *, Global *);@/
address_ptr alloc(ulong, Global *);@/
uchar *dupstr(uchar *, Global *);@/
long tonumber(uchar *, Global *);@/
uchar *skip_whitespace(uchar *);@/
long escape(long);@/
bool terminator(uchar, uchar *, bool);@/
uchar *read_item(uchar *, long, uchar *, uchar *);@/
bool exec_expr(ExprNode *, uchar *, struct ExAllData *, Global *);@/
bool lformat(uchar *, uchar *,struct ExAllData *, Global *);@/
bool exec_cmd(uchar *, uchar *, bool, Global *);@/
bool exec_Rexx(uchar *file, uchar *cmd_str, struct ExAllData *, Global *);@/
uchar *long_to_str(uchar *, long);@/
long setRexxNumVar(struct RexxMsg *, uchar *, uchar *, uchar *, long);@/
long handle_msg(struct RexxMsg *, uchar *, uchar *,
		  struct ExAllData *, Global *);

@* \[10] Index.
This is a list of the identifiers used in \FIND, and where they
appear. Underlined entries indicate the place of definition. Error
messages are shown too.
