added everything to src
[henge/apc.git] / src / scanner.c
diff --git a/src/scanner.c b/src/scanner.c
new file mode 100644 (file)
index 0000000..55245af
--- /dev/null
@@ -0,0 +1,255 @@
+/*!@file
+  \brief   APC Directory Scanner
+  \details This hand-written parser/scanner traverses a directory tree and
+           tokenizes elements of the structure which correspond to APC grammar.
+           The parser is implemented as a 2D stack which populates a list of
+           child directories at each depth, handling only the leaf nodes
+           (regular files) of the directory open at the current depth to
+           conserve memory and speed up traversal.
+           The scanner works with the lexer to lexically analyze text, and
+           assumes the existence of an external 'lex' function
+  \author  Jordan Lavatai
+  \date    Aug 2016
+  ----------------------------------------------------------------------------*/
+/* Standard */
+#include <stdio.h>  //print
+#include <string.h> //strncmp
+#include <errno.h>  //errno
+#include <ctype.h>  //tolower
+/* Posix */
+#include <err.h>    //warnx
+#include <stdlib.h> //exit
+#include <unistd.h> //chdir
+#include <dirent.h> //opendir
+#include <unistr.h> //unicode strings
+/* Internal */
+#include "parser.tab.h"
+/* Public */
+int   scanner_init(void);
+void  scanner_quit(void);
+int   scanner(void);
+int   scanner_scanpixels(int*,int);
+/* Private */
+extern //lexer.c
+int   lexer_lexstring(const uint8_t*);
+extern //lexer.c
+void  lexer_pushtok(int, int);
+static
+int   dredge_current_depth(void);
+/* Mem */
+extern //lexer.c
+struct dirent* lexer_direntpa[], **lexer_direntpp;
+extern //SRC_DIR/bin/tools/apc.c
+const char* cargs['Z'];
+#ifndef DL_STACKSIZE
+#define DL_STACKSIZE     64
+#endif
+#ifndef DL_CD_STACKSIZE
+#define DL_CD_STACKSIZE  DL_STACKSIZE //square tree
+#endif
+static
+struct dirlist
+{ DIR*           dirp;
+  struct dirent* child_directory_stack[DL_CD_STACKSIZE],** cds;
+} directory_list_stack[DL_STACKSIZE + 1],* dls; //+1 for the root dir
+static
+FILE* current_open_file = NULL;
+
+/* Directory Listing Stack
+   FILO Stack for keeping an open DIR* at each directory depth for treewalk.
+   This stack is depth-safe, checking its depth during push operations, but not
+   during pop operations, to ensure the thread doesn't open too many files at
+   once (512 in c runtime), or traverse too far through symbolic links.
+   A directory listing includes a DIR* and all DIR-typed entity in the directory
+   as recognized by dirent, populated externally (and optionally).
+   This stack behaves abnormally by incrementing its PUSH operation prior to
+   evaluation, and the POP operations after evaluation.  This behavior allows
+   the 'DL_CURDEPTH' operation to map to the current element in the 'dl_stack'
+   array, and it is always treated as the "current depth". This also allows us
+   to init the root directory to 'directory_list_stack'[0] and pop it in a safe
+   and explicit manner.
+*/
+#define DL_STACK        (directory_list_stack)
+#define DL_STACKP       (dls)
+#define DL_CD_STACK     ((*DL_STACKP).child_directory_stack)
+#define DL_CD_STACKP    ((*DL_STACKP).cds)
+#define DL_CURDIR()     ((*DL_STACKP).dirp)
+#define DL_LEN()        (DL_STACKP - DL_STACK)
+#define DL_CD_LEN()     (DL_CD_STACKP - DL_CD_STACK)
+#define DL_INIT()       (DL_STACKP = DL_STACK)
+#define DL_CD_INIT()    (DL_CD_STACKP = DL_CD_STACK)
+#define DL_POP()        ((*DL_STACKP--).dirp)
+#define DL_CD()         (*DL_CD_STACKP)
+#define DL_CD_CURNAME() (DL_CD()->d_name)
+#define DL_CD_POP()     (*--DL_CD_STACKP)
+#define DL_PUSH(D)      ((*++DL_STACKP).dirp = D)
+#define DL_CD_PUSH(E)   (*DL_CD_STACKP++ = E)
+
+
+/* Initializer
+   Initializer expects a function pointer to its lexical analysis function.
+   Sets up stack pointers and returns boolean true if 'opendir' encounters an
+   error, or if dredge_current_depth returns boolean true.
+*/
+int scanner_init
+#define CWDSTR  "./"
+#define ROOTDIR (cargs['d'] ? cargs['d'] : CWDSTR)
+()
+{ DL_INIT();
+  DL_STACK[0].dirp = opendir(ROOTDIR);
+  if (current_open_file != NULL)
+    { fclose(current_open_file);
+      current_open_file = NULL;
+    }
+  printf("Root dir %s\n",ROOTDIR);
+  return !chdir(ROOTDIR) && (DL_STACK[0].dirp == NULL || dredge_current_depth() == -1);
+}
+
+/* Quit */
+void scanner_quit
+()
+{ if (DL_CURDIR())
+    closedir(DL_CURDIR());
+}
+
+/* Scanner
+   The main driver of the scanner will advance the current treewalk state and
+   tokenize tree-based push/pop operations.  It will call 'lexer_lex' to
+   tokenize directory names prior to making a push operation. safe checking for
+   all returns from the filesystem handler will exit on serious system errors.
+
+   after pushing a new directory to the directory list, the scanner will dredge
+   the directory and alphabetically sort all file entries into the lexer's file
+   array, while placing all subdirectory entries in the current depth's child
+   directory stack to be scanned later.
+
+   Returns the number of tokens generated on success, -1 on error.
+*/
+int scanner
+#define $($)#$ //stringifier
+#define ERR_CHILD  "Fatal: Maximum of " $(DL_CD_STACKSIZE)                  \
+  " child directories exceeded for directory at depth %i\n"                \
+  ,DL_LEN()
+#define ERR_DEPTH  "Fatal: Maximum directory depth of " $(DL_STACKSIZE)     \
+  " exceeded during directory scan\n"
+#define ERR_DL     "Fatal: Directory List Stack Corruption %x\n", DL_LEN()
+()
+{ int ntok = 0;
+ scan:
+  if (DL_CD_LEN() >= DL_CD_STACKSIZE)//fail if maxchildren exceeded
+    { fprintf(stderr, ERR_CHILD);
+      goto fail;
+    }
+  if (DL_CD_LEN() > 0)               //There are entities to process
+    { if (DL_CD_POP() == NULL)            //If the dirent is null, then the
+        goto libfail;                       //lib function in dirent has failed
+<<<<<<< HEAD
+      ntok += lexer_lexstring(DL_CD_CURNAME());//lex the directory name
+=======
+      ntok += lexer_lexstring(DL_CD_CURNAME());  //lex the directory name
+>>>>>>> 15d3ab5e7871ff459af13089b82bf5f17f731ebd
+      if (DL_LEN() >= DL_STACKSIZE)       //fail if maxdepth exceeded
+        { fprintf(stderr, ERR_DEPTH);
+          goto fail;
+        }
+      if (chdir(DL_CD_CURNAME()))         //move into the new directory
+       goto libfail;
+      if (DL_CURDIR() == NULL)            //open the cwd
+       goto libfail;
+      lexer_pushtok(CLOPEN, 0);           //Push "Open Directory" token
+      ntok++;
+      return dredge_current_depth();      //Filter and sort the current depth
+    }
+  else if (DL_LEN() >= 0)            //Any dirs left? (Including root)
+    { if (closedir(DL_POP()))             //close the directory we just left
+        goto libfail;
+      if (DL_LEN() == -1)                 //If we just popped root,
+        goto done;                          //we're done
+      lexer_pushtok(CLCLOSE, 0);          //Else push "Close Directory" token,
+      ntok++;
+      if (!chdir(".."))                   //move up a directory and
+        goto scan;                          //start over
+    }
+  fprintf(stderr, ERR_DL);
+ libfail:
+  perror("scanner: ");
+ fail:
+  return -1;
+ done:
+  return ntok;
+}
+
+/* Scan Pixels 
+   Scans up to 'len' pixels from the current file into 'buf'.
+   Returns the number of pixels scanned from the file, or -1 on error
+*/
+int scanner_scanpixels
+( int*  buf,
+  int   max_len
+)
+{ static int col_len, row_len, row;
+  //Open the current file if not yet open
+  if (current_open_file == NULL) 
+    { if ((current_open_file = fopen(DL_CD_CURNAME(),"rb")) == NULL)
+       { perror("fopen: ");
+         return -1;
+        }
+      //Verify file header, get row_len/col_len
+      //if (read_img_header(&row_len, &col_len))
+      //return -1;
+      row = 0;
+    }
+  //Read pixels into the buffer if there are rows left in the image
+  if (row++ < row_len)
+    //TODO: return read_img_pixels(buf, col_len);
+    printf("SCANPIXELS NOT IMPLEMENTED\n.");
+  //Close the file and return 0
+  fclose(current_open_file);
+  current_open_file = NULL;
+  return 0;
+}
+
+/* Directory Entity Sort and Filter (Dredge)
+   This filter removes all unhandled file types, and places any 'DT_DIR' type
+   files in the current Directory List's directory stack.  Upon finishing,
+   the 'CE_STACK' is sorted alphabetically, and the current 'DL_CD_STACK' is
+   populated.  Prints warnings for unhandled files.
+
+   Returns -1 if 'readdir' encounters an error, otherwise returns the number of
+   directory entries sent to the external 'lexer_direntpa' array.
+*/
+typedef //so we can typecast dirent's 'alphasort()' to take const void*s
+int (*qcomp)(const void*, const void*);
+static inline
+int dredge_current_depth
+#define READDIR_ERROR (-1)
+#define READDIR_DONE  (0)
+#define DPS_LEN()     (lexer_direntpp - lexer_direntpa)
+#define DPS_PUSH(E)   (*lexer_direntpp++ = E)
+()
+{ struct dirent**  direntpp = lexer_direntpa;
+  DIR*             cwd      = DL_CURDIR();
+  struct dirent*   direntp;
+  DL_CD_INIT();
+ scan_next:
+  if ((direntp = readdir(cwd)) != NULL)
+    { switch (direntp->d_type)
+        { case DT_REG:
+            DPS_PUSH(direntp);
+            goto scan_next;
+          case DT_DIR:
+           if (*(direntp->d_name) == '.') //skip hidden files and relative dirs
+              goto scan_next;
+           DL_CD_PUSH(direntp);
+            goto scan_next;
+          case DT_UNKNOWN:
+            warnx("unknown file %s: ignoring", direntp->d_name);
+          default:
+            goto scan_next;
+        }
+    }
+  if (errno)
+    return -1;
+  qsort(lexer_direntpa, DPS_LEN(), sizeof direntp, (qcomp)alphasort);
+  return DPS_LEN();
+}