home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / cpp_libs / cmdline.lha / cmdline / src / cmd / syntax.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-08-03  |  11.0 KB  |  373 lines

  1. //------------------------------------------------------------------------
  2. // ^FILE: syntax.c - implement the ArgSyntax class
  3. //
  4. // ^DESCRIPTION:
  5. //    This file uses a SyntaxFSM to implement a class to parse an argument
  6. //    syntax string from input and to hold the "compiled" result.
  7. //
  8. // ^HISTORY:
  9. //    03/25/92    Brad Appleton    <brad@ssd.csd.harris.com>    Created
  10. //-^^---------------------------------------------------------------------
  11.  
  12. #include <stdlib.h>
  13. #include <iostream.h>
  14. #include <string.h>
  15. #include <ctype.h>
  16.  
  17. #include <cmdline.h>
  18.  
  19. #include "syntax.h"
  20. #include "quoted.h"
  21.  
  22. //------------------------------------------------------------------ copy_token
  23.  
  24. //-------------------
  25. // ^FUNCTION: copy_token - copy into a token
  26. //
  27. // ^SYNOPSIS:
  28. //    copy_token(dest, src)
  29. //
  30. // ^PARAMETERS:
  31. //    const char * & dest;
  32. //    -- where to house the duplicated token
  33. //
  34. //    SyntaxFSM::token_t src;
  35. //    -- the token to copy.
  36. //
  37. // ^DESCRIPTION:
  38. //    Duplicate the token denoted by "src" into "dest".
  39. //
  40. // ^REQUIREMENTS:
  41. //    None.
  42. //
  43. // ^SIDE-EFFECTS:
  44. //    Allocates storage for "dest" is token length is non-zero.
  45. //
  46. // ^RETURN-VALUE:
  47. //    None.
  48. //
  49. // ^ALGORITHM:
  50. //    Trivial.
  51. //-^^----------------
  52. void
  53. copy_token(const char * & dest, SyntaxFSM::token_t src)
  54. {
  55.    char * tok = new char[src.len + 1] ;
  56.    ::strncpy(tok, src.start, src.len);
  57.    tok[src.len] = '\0';
  58.    dest = tok;
  59. }
  60.  
  61. //---------------------------------------------------------------- ArgSyntax
  62.  
  63. //-------------------
  64. // ^FUNCTION: parse_syntax - parse syntax string
  65. //
  66. // ^SYNOPSIS:
  67. //    parse_syntax(str)
  68. //
  69. // ^PARAMETERS:
  70. //    const char * str;
  71. //    -- the string (containing the argument syntax) to parse.
  72. //
  73. // ^DESCRIPTION:
  74. //    Parse the syntax-string and compile it into an internal format
  75. //    (namely an ArgSyntax object).
  76. //
  77. // ^REQUIREMENTS:
  78. //    "str" should correspond to the following:
  79. //
  80. //       [<KEYWORD-SPEC>] [<VALUE-SPEC>]
  81. //
  82. //    Where <KEYWORD-SPEC> is of the form:
  83. //       c|keyword
  84. //
  85. //    Where 'c' is the option-character and "keyword" is the keyword.
  86. //
  87. //    (There must be no spaces surrounding the '|', if there arem then a space
  88. //    before the '|' means an "empty" option and a space after the '|' means
  89. //    an empty keyword).
  90. //
  91. //    <VALUE-SPEC> should look like:
  92. //        value [...]
  93. //
  94. //    Where "value" is the value name and "..." indicates the value is really
  95. //    a list of values. The entire VALUE-SPEC should be surrounded by '[' and
  96. //    ']' if the value is optional.
  97. //
  98. //    If the argument itself is optional then the entire syntax string
  99. //    should be inside of square brackets.
  100. //
  101. //    Lastly - a positional AND keyword argument may be denoted by
  102. //        "[c|keyword] value"
  103. //
  104. // ^SIDE-EFFECTS:
  105. //    - modifies all parts of the ArgSyntax object.
  106. //    - prints syntax error messages on cout.
  107. //
  108. // ^RETURN-VALUE:
  109. //    None.
  110. //
  111. // ^ALGORITHM:
  112. //    Too complicated to be described here - follow along.
  113. //-^^----------------
  114. int
  115. ArgSyntax::parse_syntax(const char * syntax)
  116. {
  117.    const char * ptr = syntax;
  118.    SyntaxFSM  fsm;
  119.    SyntaxFSM::token_t  token;
  120.  
  121.    while (fsm(ptr, token)) {
  122.       switch(fsm.state()) {
  123.       case  SyntaxFSM::OPTION :
  124.          // We have an option character - save it and move on
  125.          if (token.len)  arg_char = *(token.start) ;
  126.          if (! fsm.level())  arg_syntax |= CmdArg::isREQ;
  127.          break;
  128.  
  129.       case  SyntaxFSM::KEYWORD :
  130.          // We have a keyword - save it and move on
  131.          ::copy_token(arg_keyword, token);
  132.          if (! fsm.level())  arg_syntax |= CmdArg::isREQ;
  133.          break;
  134.  
  135.       case  SyntaxFSM::VALUE :
  136.          // We have a value - save it and call parse_value to
  137.          // figure out what the flags are.
  138.          //
  139.          if (token.len)  ::copy_token(arg_value, token);
  140.          parse_value(fsm);
  141.          break;
  142.  
  143.       case  SyntaxFSM::LIST :
  144.          // We have an ellipsis -- update the syntax flags
  145.          arg_syntax |= CmdArg::isLIST;
  146.          break;
  147.  
  148.       case  SyntaxFSM::ERROR :
  149.          // Error!
  150.          cerr << "syntax error in \"" << syntax << "\"." << endl ;
  151.          return  -1;
  152.  
  153.       default :
  154.          cerr << "internal error in class SyntaxFSM.\n\tunexpected state "
  155.               << "(" << fsm.state() << ") encountered." << endl ;
  156.          return  -1;
  157.       } //switch
  158.    } //while
  159.  
  160.    return  0;
  161. }
  162.  
  163.  
  164. //-------------------
  165. // ^FUNCTION: parse_value - parse an argument value
  166. //
  167. // ^SYNOPSIS:
  168. //    parse_value(fsm)
  169. //
  170. // ^PARAMETERS:
  171. //    const SyntaxFSM & fsm;
  172. //    -- the finite-state machine that is reading input.
  173. //
  174. // ^DESCRIPTION:
  175. //    The "value" has already been read and saved, we need to figure out
  176. //    what syntax_flags to associate with the argument.
  177. //
  178. // ^REQUIREMENTS:
  179. //    "fsm" MUST be in the SyntaxFSM::VALUE state!
  180. //
  181. // ^SIDE-EFFECTS:
  182. //    Modifies the arg_syntax flags of an ArgSyntax object.
  183. //
  184. // ^RETURN-VALUE:
  185. //    None.
  186. //
  187. // ^ALGORITHM:
  188. //    Too complicated to be described here - follow along.
  189. //
  190. //-^^----------------
  191. void
  192. ArgSyntax::parse_value(const SyntaxFSM & fsm)
  193. {
  194.    // Each of the possibilities we encounter in the SyntaxFSM::VALUE state
  195.    // will correspond to some combination of num_tokens, num_braces, and
  196.    // level. Let us determine all the valid possibilites below:
  197.    //
  198.    //   (num_tokens, num_braces, level)            syntax-string
  199.    //   -------------------------------     ---------------------------
  200.    //             (1, 0, 0)                      "value"
  201.    //             (1, 0, 1)                      "[value]"
  202.    //             (3, 0, 0)                      "c|string value"
  203.    //             (3, 0, 1)                      "c|string [value]"
  204.    //             (3, 0, 1)                      "[c|string value]"
  205.    //             (3, 0, 2)                      "[c|string [value]]"
  206.    //             (3, 1, 0)                      "[c|string] value"
  207.    //             (3, 1, 1)                      "[c|string] [value]"
  208.    //             (3, 1, 1)                      "[[c|string] value]"
  209.    //
  210.    // There are only two case where a given (num_token, num_braces, level)
  211.    // combination corresponds to more than one possible syntax-string. These
  212.    // two cases are (3, 0, 1) and (3, 1, 1). We can ignore the "ambiguity"
  213.    // of (3, 1, 1) because although the two possible syntax-strings are
  214.    // different, they mean exactly the same thing. (3, 0, 1) is a different
  215.    // case however: how do we tell if the whole argument is optional or if
  216.    // just the value is optional? If the whole argument is required (meaning
  217.    // "not optional") then we will already have set the isREQ flag when we
  218.    // parsed the option and/or the keyword name.
  219.    //
  220.    if (fsm.num_tokens() == 1) {
  221.       // cases (1, 0, 0) and (1, 0, 1)
  222.       arg_syntax |= CmdArg::isPOS;
  223.       if (! fsm.level()) {
  224.          arg_syntax |= (CmdArg::isREQ | CmdArg::isVALREQ);
  225.       } else {
  226.          arg_syntax |= (CmdArg::isOPT | CmdArg::isVALOPT);
  227.       }
  228.    } else {
  229.       if (fsm.num_braces()) {
  230.          // cases (3, 1, 0) and (3, 1, 1)
  231.          arg_syntax |= CmdArg::isPOS;
  232.          if (! fsm.level()) {
  233.             // case (3, 1, 0)
  234.             arg_syntax |= (CmdArg::isREQ | CmdArg::isVALREQ);
  235.          } else {
  236.             // case (3, 1, 1)
  237.             arg_syntax |= (CmdArg::isOPT | CmdArg::isVALOPT);
  238.          }
  239.       } else {
  240.          if (! fsm.level()) {
  241.             // case (3, 0, 0)
  242.             arg_syntax |= (CmdArg::isREQ | CmdArg::isVALREQ);
  243.          } else if (fsm.level() == 1) {
  244.             // case (3, 0, 1)
  245.             if (arg_syntax & CmdArg::isREQ) {
  246.                arg_syntax |= CmdArg::isVALOPT;
  247.             } else {
  248.                arg_syntax |= CmdArg::isVALREQ;
  249.             }
  250.          } else {
  251.             // case (3, 0, 2)
  252.             arg_syntax |= CmdArg::isVALOPT;
  253.          } //if level
  254.       } //if num-braces
  255.    } //if num-tokens
  256. }
  257.  
  258.  
  259. //-------------------
  260. // ^FUNCTION: parse_flag - parse a flag
  261. //
  262. // ^SYNOPSIS:
  263. //    parse_flag(is)
  264. //
  265. // ^PARAMETERS:
  266. //    istream & is;
  267. //    -- the input stream to read the flag from.
  268. //
  269. // ^DESCRIPTION:
  270. //    By specifying a string that is accepted by "parse_syntax" one
  271. //    can specify almost any combination of CmdArg::SyntaxFlags. 
  272. //    The only ones that cannot be specified in this manner are the
  273. //    CmdArg::isVALSTICKY and CmdArg::isVALSEP flags. In order to
  274. //    specify these flags, we allow the syntax string to be followed
  275. //    by a colon (':') and one of "SEPARATE" or "STICKY".
  276. //
  277. // ^REQUIREMENTS:
  278. //    None.
  279. //
  280. // ^SIDE-EFFECTS:
  281. //    - modifies the syntax-flags of an ArgSyntax object.
  282. //    - prints syntax error messages on stderr.
  283. //    - modifies the state of "is" if an error occurs.
  284. //    - consumes characters from is.
  285. //
  286. // ^RETURN-VALUE:
  287. //    A reference to the input stream used.
  288. //
  289. // ^ALGORITHM:
  290. //    Trivial.
  291. //-^^----------------
  292. istream &
  293. ArgSyntax::parse_flag(istream & is)
  294. {
  295.    char  ch;
  296.    is >> ch;
  297.    if (! is)  return  is;
  298.  
  299.       // If `ch' is a quote then the flags were omitted
  300.    if ((ch == '\'') || (ch == '"')) {
  301.       is.putback(ch);
  302.       return  is ;
  303.    }
  304.  
  305.       // The flags are here, make sure they start with ':'
  306.    if (ch != ':') {
  307.       cerr << "Unexpected token after syntax string.\n"
  308.            << "\texpecting a colon, or a double or single quote." << endl ;
  309.       is.clear(ios::failbit);
  310.       return  is;
  311.    }
  312.  
  313.       // Now parse the flag
  314.    char  arg_flag[16];
  315.    is.width(sizeof(arg_flag) - 1);
  316.    is >> arg_flag;
  317.    if (! is) {
  318.       if (is.eof()) {
  319.          cerr << "Error - premature end-of-input.\n"
  320.               << "\texpecting one of \"sticky\" or \"separate\"." << endl ; 
  321.       } else {
  322.          cerr << "Unable to extract argument flag." << endl ;
  323.       }
  324.       return  is;
  325.    }
  326.  
  327.    char * flag = arg_flag;
  328.  
  329.       // Skip any leading "CmdArg::isVAL" portion of the flag      
  330.    if (CmdLine::strmatch("Cmd", flag, 3) == CmdLine::str_EXACT)  flag += 3;
  331.    if (CmdLine::strmatch("Arg", flag, 3) == CmdLine::str_EXACT)  flag += 3;
  332.    if (CmdLine::strmatch("::", flag, 2) == CmdLine::str_EXACT)   flag += 2;
  333.    if (CmdLine::strmatch("is", flag, 2) == CmdLine::str_EXACT)   flag += 2;
  334.    while ((*flag == '_') || (*flag == '-'))  ++flag;
  335.    if (CmdLine::strmatch("VAL", flag, 3) == CmdLine::str_EXACT)  flag += 3;
  336.    while ((*flag == '_') || (*flag == '-'))  ++flag;
  337.  
  338.       // check for an ambiguous flag
  339.    if (((*flag == 's') || (*flag == 'S')) && (! *(flag + 1))) {
  340.       cerr << "Ambiguous flag \"" << flag << "\"." << endl ;
  341.       is.clear(ios::failbit);
  342.       return  is;
  343.    }
  344.  
  345.    if (CmdLine::strmatch("Sticky", flag) != CmdLine::str_NONE) {
  346.       arg_syntax |= CmdArg::isVALSTICKY ;
  347.    } else if (CmdLine::strmatch("Separate", flag) != CmdLine::str_NONE) {
  348.       arg_syntax |= CmdArg::isVALSEP ;
  349.    } else {
  350.       cerr << "Invalid flag \"" << flag << "\".\n"
  351.            << "\tmust be one of \"sticky\" or \"separate\"." << endl ;
  352.       is.clear(ios::failbit);
  353.       return  is;
  354.    }
  355.  
  356.    return  is ;
  357. }
  358.  
  359. //------------------------------------------------------------------ operator>>
  360.  
  361. istream &
  362. operator>>(istream & is, ArgSyntax & arg)
  363. {
  364.    QuotedString  qstr(256);
  365.  
  366.    is >> qstr ;
  367.    if (! is)  return  is;
  368.  
  369.    if (arg.parse_syntax(qstr))  return  is;
  370.    return  arg.parse_flag(is);
  371. }
  372.  
  373.