ALGORITHMS AND TEST CASES: -------------------------- main(): Return value (int): 0 indicates success, nonzero failure. Arguments: argc (int, number of command-line arguments) argv (char *[], array of command-line arguments) Variables: filename (const char *, filename to open) props (Properties, the main property list) command (string, the command input) finished (bool, is the user finished?) 1. How many command-line arguments? 0: filename = proptest.prop 1: filename = first argument >1: display usage and return failure. 2. Print filename 3. Call props.load() with filename If a Properties::CannotOpen exception was thrown, print an error message. 4. showmenu() 5. Prompt user, read command 6. finished = the result of calling docommand() on command and props. 7. If finished == false, go to step 5. 8. return success. showmenu(): 1. Print menu. docommand(): Return value (bool): true indicates that the user wishes to quit. false indicates that the user wishes to continue. Arguments: command (const string &, the command typed by the user) props (Properties &, the Properties object on which to operate) Variables: cmd (string, a modifiable copy of command) property (string, property name typed by the user) value (string, property value typed by the user) 1. cmd = command 2. Strip leading and trailing whitespace from cmd with function stripws(). 3. Convert cmd to lowercase with function lowercasify() 4. What is the value of cmd? "quit": return true "print": print the Properties object to stdout with function operator<<() [syntax: cout << props] "add": a. Prompt user and input property b. Prompt user and input value c. Call props.addProperty() on property and value. If a Properties::BadPropery exception was thrown, print an error message. "get": a. Prompt user and input property b. Call props.getValueFor() on property. If a Properties::NotFound exception was thrown, print an error message. "remove": a. Prompt user and input property b. Call props.remove() on property. If a Properties::NotFound exception was thrown, print an error message. c. Print the old value of the property (returned by remove()). "save": Variable: filename (string, name with which to save file) a. Prompt user and input filename. b. Call props.save() on filename. If a Properties::CannotOpen exception was thrown, print an error message "help": a. Call showmenu() anything else: a. Print error message 5. Return false. stripws(): Arguments: str (string &, string on which to operate) Variables: last_nonws (unsigned, last non-whitespace character in string) 1. Erase all characters in str from the beginning, up to but not including the first non-whitespace character. 2. last_nonws = last non-whitespace character. 3. If last_nonws is not the last character, erase all characters after last_nonws. lowercasify(): Arguments: str (string &, string on which to operate) 1. For each character str[i] in the string: str[i] = str[i] converted to lowercase (with tolower) operator<<(): Return value (ostream &): ostream to which we output. Arguments: out (ostream &, stream to which we output) prop (const Properties &, object to output) 1. For each proppair `i' in prop.plist: Call Properties::writePropLine() on out, i.first, and i.second. 2. Return out. class Properties: typedefs: proppair = pair [a (property,value) pair] proplist = list [a property list] chartype = enum {...} [used by match_prop] Data members: plist (proplist, list of (property,value) pairs) Properties::Properties() (default): 1. Allocate memory for plist. Properties::Properties() (copy): Arguments: old (const Properties &, object to copy) 1. Allocate plist as a copy of old.plist. list<>'s copy constructor is assumed to implement a deep copy. Properties::Properties(): Arguments: filename (const char *, filename to load) Throws: Properties::CannotOpen 1. Call the default constructor. 2. Call load() on filename. Properties::~Properties(): 1. If plist is non-NULL, delete it. operator=(): Return value (Properties &): The object on which operator=() was called. Arguments: rhs (const Properties &, right-hand side of assignment expression) 1. If plist is non-NULL, delete it. 2. Allocate plist as a copy of rhs.plist. list<>'s copy constructor is assumed to implement a deep copy. 3. Return this object. Properties::load(): Arguments: filename (const char *, name of file to load) Throws: Properties::CannotOpen Variables: file (istream, stream opened on filename) property (string, property name from current line) value (string, property value from current line) lineno (int, (zero-indexed) line number) errno (extern int, system error number) 1. If plist is non-NULL, delete and re-allocate it. RATIONALE: we don't want the properties from two files to be commingled. 2. Open file on filename (with open()). 3. If file indicates failure, throw a CannotOpen exception with errno and filename. 4. If file is at eof, go to step 8. 5. Call readPropLine on file, property, and value. If it succeeds, call addProperty on property and value. If a Properties::BadLine exception was thrown, print an error message. If a Properties::BadProperty exception was thrown, print a cryptic error message (this should have resulted in a BadLine instead) 6. Increment lineno. 7. Go to step 4. 8. Close file. Properties::getValueFor(): Return value (string): value of requested property. Arguments: propname (const string &, name of property to get value for) Throws: Properties::NotFound 1. For each proppair `i' in plist: If i.first == propname, return i.second 2. throw a NotFound exception with propname. Properties::getBoolValueFor(): Return value (bool): value of requested property. Arguments: propname (const string &, name of property to get value for) Throws: Properties::NotFound Properties::BadFormat Variables: val (string, value of property) 1. val = getValueFor(propname). 2. Call stripws and lowercasify on val. 3. What is val? "1", "on", or "true": Return true. "0", "off", or "false": Return false. anything else: Throw a BadFormat exception with propname and val. Properties::getIntValueFor(): Return value (int): value of requested property. Arguments: propname (const string &, name of property to get value for) Throws: Properties::NotFound Properties::BadFormat Variables: val (string, value of property) endptr (char *, pointer to the last character strtol matched) l (long, result of strtol) 1. val = getValueFor(propname). 2. Call stripws and lowercasify on val. 3. l = the result of calling strtol on val.c_str(), &endptr, and 0. 4. if endptr points to a NUL, and the first character of val is not NUL, return l (cast to an int). 5. Throw a BadFormat exception with propname and val. Properties::getDoubleValueFor(): Return value (double): value of requested property. Arguments: propname (const string &, name of property to get value for) Throws: Properties::NotFound Properties::BadFormat Variables: val (string, value of property) endptr (char *, pointer to the last character strtol matched) d (double, result of strtod) 1. val = getValueFor(propname). 2. Call stripws and lowercasify on val. 3. d = the result of calling strtod on val.c_str(), and &endptr. 4. if endptr points to a NUL, and the first character of val is not NUL, return d. 5. Throw a BadFormat exception with propname and val. Properties::addProperty(): Return value (string): old value of the requested property ("" if it did not exist). Arguments: propname (const string &, name of property to add) value (const string &, value to give that property) Throws: Properties::BadProperty Variables: old (string, previous value of the property) 1. old = "" 2. If valid_property(propname) is false, throw a BadProperty exception with propname. 3. For each proppair `i' in plist: If i.first == propname, old=i.second, i.second=value, and return old. 4. Add a new proppair containing propname and value. 5. Return old. Properties::remove(): Return value (string): old value of the removed property. Arguments: propname (const string &, name of property to remove) Throws: Properties::NotFound Variables: old (string, previous value of the property) 1. For each proppair `i' in plist: If i.first == propname a. old = i.second b. remove i from plist c. return old. 2. Throw a NotFound exception with propname. Properties::isDefined(): Return value (bool): true if the property was found, false otherwise. Arguments: propname (const string &, name of property for which to search) Throws: Properties::NotFound 1. For each proppair `i' in plist: If i.first == propname, return true. 2. Return false. Properties::save(): Arguments: filename (const char *, name with which to save file) Variables: file (ofstream, stream opened on filename) Throws: Properties::CannotOpen 1. Open file on filename. 2. If file indicates failure, throw a CannotOpen exception with errno and filename. 3. Call operator<< on file and the current object [syntax: file << *this] 4. Close file. Properties::readPropLine(): Return value (bool): true if the line contained a property and value, false otherwise. Arguments: in (istream &, istream from which we read the line) propname (string &, string which will hold the property name) value (string &, string which will hold the property value) Throws: Properties::BadLine Variables: line (string, the line read from in) matched (int, number of characters matched) 1. Read a line from in into line. 2. Call stripws on line. 3. If line is empty, or if the first character of line is '#', return false. 4. matched = match_prop(line, propname) [see below] 5. If matched == 0, throw a BadLine exception with line. 6. Erase the first (matched) characters of line. 7. Call stripws on line. 8. value = line. 9. return true. Properties::writePropLine(): Arguments: out (ostream &, ostream to which we write the line) propname (const string &, property name) value (const string &, property value) 1. Print propname, an equal sign, value, and a newline to out. Properties::valid_property(): Return value (bool): true if prop is a valid property name, false otherwise. Arguments: prop (const string &, property name to test) Variables: test (string, writable copy of prop) mprop (string, property name according to match_prop) 1. test = prop 2. If prop's first or last character is whitespace, or if prop is empty, return false. 3. Append "=" to the end of test. 4. Call match_prop on test and mprop. 5. if mprop == prop, return true; else, return false. Properties::match_prop(): Return value (int): Number of characters matched (including whitespace and '=') Arguments: str (const string &, "property=value" line to decode) prop (string &, string which will hold the property name) Constants: next_state (int[][], state table for the DFA) FAIL (int, = -1, failing state) ACCEPT (int, = 9, accepting state) Variables: state (int, current state) pos (unsigned, current position in string) matched (int, number of characters matched so far) nextchar (char, character currently under consideration) in (chartype, character type of nextchar) 1. state = 0, pos = 0, matched = 0, prop = "". 2. if pos is past the end of str, prop = "" and return 0. 3. nextchar = str[pos], increment pos. 4. in = character type of nextchar. 5. state = next_state[state][in]. 6. increment matched. 7. if state == FAIL, prop = "" and return 0. 8. if state == ACCEPT, goto step 11. 9. if in != whitespace, append nextchar to prop 10. go to step 2. 11. return matched. Note: The state table is as follows: \ CURR IN \ 0 1 2 3 +-------------------------------- alpha | 1 1 1 FAIL num | FAIL 1 1 FAIL dot | FAIL 2 FAIL FAIL ws | 0 3 FAIL 3 equal | FAIL ACCEPT FAIL ACCEPT other | FAIL FAIL FAIL FAIL TEST CASES (found in proptest.prop in ~nmoor0/CS216/misc/): ----------------------------------------------------------- window.colour = purple debug =off # The following has two consecutive dots. invalid..property = value # No value bad line # Some blank lines # Lots of whitespace, including tabs. verbose = on # A redefinition; also, no whitespace. verbose=off # Property names may not contain spaces. this property = bad # This should set the property "foo" to "bar=baz" foo=bar=baz This file should cause the following warnings: bad line 4 (invalid..property = value) bad line 7 (bad line) bad line 19 (this property = bad) It should create a property list with the following properties: property | value --------------+----------- window.colour | purple debug | off verbose | off foo | bar=baz