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