X-Git-Url: https://www.kengrimes.com/gitweb/?p=henge%2Fapc.git;a=blobdiff_plain;f=src%2Fscanner.c;fp=src%2Fscanner.c;h=55245af5952a370685155d195c24fb8afb662f39;hp=0000000000000000000000000000000000000000;hb=1c75266c959f8168fb6a73b6fef22fc91a5affc7;hpb=a58c4564255146d9a9c7d4ee095558c91eaa874e diff --git a/src/scanner.c b/src/scanner.c new file mode 100644 index 0000000..55245af --- /dev/null +++ b/src/scanner.c @@ -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 //print +#include //strncmp +#include //errno +#include //tolower +/* Posix */ +#include //warnx +#include //exit +#include //chdir +#include //opendir +#include //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(); +}