www.pudn.com > CMDL.zip > CMDL.CPP


/*************************************************************** 
File: CMDL.CPP                Copyright 1992 by Dlugosz Software 
part of the CMDL package for command-line parsing 
This version may be used freely, with attribution. 
***************************************************************/ 
 
#include "usual.h" 
#include "cmdl.h" 
#include "scanner.h" 
#include  
#include  
#include  
 
cmdl* cmdl::head= 0; 
cmdl* cmdl::last= 0; 
#ifdef VERSION21 
 cmdl::errval cmdl::error= OK; 
#else 
 errval cmdl::error= OK; 
#endif 
int cmdl::count= 0; 
char* cmdl::signon_string= 0; 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
/*     parseing and processing              */ 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl* cmdl::find_switch (char c) 
{  //find switch matching char name 
for (cmdl* p= head;  p;  p=p->next) { 
   if (p->iskeyword()) continue; 
   if (!p->ischar()) continue; 
   // filtered out the ones I don't care about, 
   // now match the name 
   if (c == p->cname) return p;  //found it! 
   } 
return 0;  //found nothing. 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl* cmdl::find_keyword (char c) 
{  //find keyword matching char name (no leading switchchar) 
for (cmdl* p= head;  p;  p=p->next) { 
   if (!p->iskeyword()) continue; 
   if (!p->ischar()) continue; 
   // filtered out the ones I don't care about, 
   // now match the name 
   if (c == p->cname) return p;  //found it! 
   } 
return 0;  //found nothing. 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl* cmdl::find_switch (char* s) 
{  //find switch matching string name 
for (cmdl* p= head;  p;  p=p->next) { 
   if (p->iskeyword()) continue; 
   if (p->ischar()) continue; 
   // filtered out the ones I don't care about, 
   // now match the name 
   if (!strcmp (s,p->sname)) return p;  //found it! 
   } 
return 0;  //found nothing. 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl* cmdl::find_keyword (char* s) 
{  //find keyword matching string name (no leading switchchar) 
for (cmdl* p= head;  p;  p=p->next) { 
   if (!p->iskeyword()) continue; 
   if (p->ischar()) continue; 
   // filtered out the ones I don't care about, 
   // now match the name 
   if (!strcmp (s,p->sname)) return p;  //found it! 
   } 
return 0;  //found nothing. 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
bool cmdl::parse_switches (cmdlscan& s, unsigned /*flags*/) 
{ 
/* this matches the next thing in the input stream `s' against 
   all the switch entries.  It handles cascaded single-character 
   switches in one gulp. 
   Flags not used yet... I'll think of something. 
*/ 
int passcount= 0;  //how many switches were processed 
++s;  //skip over the switch character 
for (;;) {  //handle cascaded single character switches 
   char c= s.thischar(); 
   cmdl* sw= find_switch (c); 
   if (sw) { 
      passcount++; 
      count++;  //how many total 
      ++s;  //advance beyond the name 
      if (!sw->scan (s)) { 
         sw->report_error(); 
         return FALSE; 
         } 
      } 
   else break;  //did not find another switch 
   } 
if (passcount == 0) {  //try a word switch 
   const wordsize= 50;  //longest identifier possible 
   char word[wordsize]; 
   s.extract_word (word,wordsize); 
   cmdl* sw= find_switch (word); 
   if (sw) {  //found one 
      count++; 
      sw->scan (s); 
      } 
   else { 
      // switch not found 
      output ("error: switch -"); 
      output (word); 
      output ("\ninvalid switch.\n"); 
      return FALSE; 
      } 
   } 
return TRUE; 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
bool cmdl::parse_keywords (cmdlscan& s, unsigned fl) 
{ 
/* this matches the next thing input stream `s' against 
   non-switch items.  It handles positional parameters, which are 
   syntactically anonomous keyword parameters 
*/ 
int oldpos= s.mark(); 
const wordsize= 50;  //longest identifier possible 
char word[wordsize]; 
s.extract_word (word,wordsize); 
cmdl* sw= find_keyword (word); 
if (sw) {  //found it! 
   count++; 
   sw->scan (s); 
   return TRUE; 
   } 
// else check for positional parameters 
s.restore (oldpos);  //rewind the input 
sw= find_positional (fl); 
if (sw) { 
   count++; 
   sw->scan (s); 
   return TRUE; 
   } 
// if it gets to here, is an error. 
output ("bad parameter: "); 
output (word); 
output ("\n"); 
return FALSE; 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl* cmdl::find_positional (unsigned /*fl*/) 
{   // look for cmdl objects with no name 
for (cmdl* p= head;  p;  p=p->next) { 
   if (!(p->flags & noname)) continue; 
   if (p->flags & used) continue;  //used that one already 
   // got here, p is the one I want. 
   return p; 
   } 
return 0;  //did not find one 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
bool cmdl::parseit (cmdlscan& s, unsigned fl) 
{ 
/* this scans the input described by `s', and processes all 
   parameters it parses out.  It may terminate the program with 
   an error. 
*/ 
static int recursion_level= 0; 
const recursion_limit= 10; 
if (++recursion_level >= recursion_limit) { 
   /* it is possible for a parameter to create more input.  I.e. 
     an @ will read the contents of a file, and a % will read an 
     environment variable.  None of this is implemented, but it is 
     planned and provided for.  */ 
   output ("error: nesting level too deep.\n"); 
   recursion_level--; 
   return FALSE; 
   } 
for (;;) { 
   s.skipws(); 
   switch (s.thischar()) { 
      case '\0':  
         recursion_level--; 
         return TRUE;     //got through the whole string 
      case '-': 
      case '/': 
         if (!parse_switches (s,fl)) { 
            recursion_level--; 
            return FALSE; 
            } 
         break; 
      /* other things, such as the above-mentioned @ and % will 
         go here as additional cases. */ 
      default: 
         if (!parse_keywords (s,fl)) { 
            recursion_level--; 
            return FALSE; 
            } 
         break; 
      } 
   } 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
bool cmdl::parseit (char* s, unsigned fl) 
{ 
/* this parses an explicit string.  It creates the scanner object 
   around the input string, and adds processing after the scan 
   is complete. 
*/ 
cmdlscan scan (s); 
if (!parseit (scan, fl)) { 
   // return FALSE;  //depending on fl 
   exit (1); 
   } 
// otherwise look for more errors 
if (count == 0) { 
   show_usage(); 
   exit (1); 
   } 
for (cmdl* p= head;  p;  p=p->next) 
   p->post_process (fl); 
for (p= head;  p;  p=p->next) 
   p->validate (fl); 
return TRUE; 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
bool cmdl::parseit (unsigned /*fl*/) 
{ 
/* this is the form you'll normally see.  It looks up the command-line 
   tail from DOS, and passes it to the previous form. 
*/ 
static char string[257]; 
// locate the command line tail in the PSP 
extern unsigned _psp; 
char _seg * psp= (char _seg*) _psp; 
const offset= 0x80; 
char far* commandline= psp+offset; 
int len= *commandline++; 
for (int loop= 0;  loop < len;  loop++) 
   string[loop]= commandline[loop]; 
   // _fmemcpy() not available in TC++1.01 (a.k.a. "second edition").  Bummer 
string[len]= '\0'; 
return parseit (string); 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
/*     constructors and helpers             */ 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
void cmdl::linkin() 
{ 
/* this is how the parser knows about all command line objects. 
   They are maintained in a linked list.  Note that they are never 
   _removed_ from the list, so it is a real bad idea to let any 
   command line object go out of scope before calling parseit(). 
   After parsing, the list is abandoned, never to be traversed again. 
*/ 
next= 0; 
if (!head) head= last= this;  //make a list of one. 
else { 
   // chain to end of list 
   last->next= this; 
   last= this; 
   } 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl::cmdl (const char* name, const char* helpstring, unsigned fl) 
: helpstring(helpstring), flags(fl) 
{     //STRING form 
if (!name) flags |= noname;    //note positional parameters 
else { 
   int len= strlen (name); 
   if (len == 0) { 
      //note name as a char 
      flags |= charname; 
      cname= name[0]; 
      } 
   else sname= name;   //note name as a string. 
   } 
linkin(); 
} 
 
/* /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\ */ 
 
cmdl::cmdl (char name, const char* helpstring, unsigned fl) 
: helpstring(helpstring), flags(fl) 
{     //CHAR form 
// note name as a character 
flags |= charname; 
cname= name; 
linkin(); 
}