termlib.js home | multiple terminals | parser | faq | documentation | samples
Sample Parser Test
 
> open terminal  
 
 
(c) mass:werk,
N. Landsteiner 2003-2010
http://www.masswerk.at

Sample Parser
 

The parser provides a solid basis for any command evaluation for "termlib.js". It splits the current command line to arguments (words) and takes care of any white space and quotes. Even escape characters and options are supported.

 
Synopsis:

  var parser = new Parser();
  parser.parseLine(this);
  var command = this.argv[this.argc++];

 
Usage:

Call method "parseLine(this)" from inside of your Terminal-handler. This will parse the current command line to chunks separated by any amount of white space (see property whiteSpace). "parseLine" will inject the following properties into the Terminal instance:

this.argv Array of parsed chunks (words, arguments)
this.argQL Array of quoting level per argument
This array will contain the according quote character or an empty string for each element of argv with the same index.
this.argc A pointer to this.argv and this.argQL (initially set to 0; used by method getopt).

Example:

For the string: This is "quoted".
argv will result to: ['this', 'is', 'quoted', '.']
and argQL will result to: ['', '', '"', '']

(Provided that '"' is defined as a quote character. Defaults: ', ", and `)

 
getopt(this, "<options>"):

Call this method from inside of your handler to parse any options from the next chunk in this.argv that this.argc points to.
Options are considered any arguments preceded by an option character in the property optionChars (default: "-"). The second argument is a string containing all characters to be considered legal option flags.
The method returns an object with a property of type object for any option flag (defined as a character in the options-string) found in argv. argc will be advanced to the first element of argv that is not an option. Each of the flag-objects will contain the property `value'.
In case that the option flag was immediately followed by a number (unsigned float value), this will be stored as a Number in the value, otherwise the flag's value will be set to -1.
Any flags that were not defined in the options-string will be stored in an array of single characters in the property `illegals' of the returned object.

Example:

   this.argv: ["test", "-en7a", "-f", "next"]
   this.argc: set initially to 0

   var command = this.argv[this.argc++];    // this.argc now points to 2nd chunk
   var options = parser.getopt(this, "en");

getopt will return the following object:

   {
      'e': { value: -1 },
      'n': { value: 7 },
      'illegals': ['a', 'f']
   }

and this.argc will be set to 3, pointing to "next".

Note: Quoted terms are never parsed as options.

 
Escape expressions:

The parser can handle escape sequences, e.g.: hexdecimal notations of characters that are preceded by an escape character. (default: parse any 2-byte hex expressions preceded by "%" as a character, e.g. "%41" => "A".)
Escape characters are defined in the property escapeExpressions (type object) with the according method as their value. (The given function or method must be present at creation and takes four arguments: terminal instance, current char-position, the escape character found, and the current quoting level. The method or function is expected to strip any escape sequence of the lineBuffer and to return a string to be inserted at the current parsing position.)
The result of an escape expression will allways add to the current chunk and will never result in parsed white space.

 
Configuration:

You may want to overide the follow objects (or add properties):

parser.whiteSpacechars to be parsed as white space (default: space, tab)
parser.quoteCharschars to be parsed as quotes (default: ", ', `)
parser.singleEscapeschars to escape a quote or escape expression (default: \)
parser.optionCharschars that start an option (default: -)
parser.escapeExpressionschars that start escape expressions (default: % => 2-byte hex)

 
Notes:
v. 1.1: Parser is now a function with a constructor.
Configurations can be handled at a per-instance basis.
Parser is now a single, self-contained object in the global name space.
Note on the implementation: We are not storing the terminal instance in order to avoid memory leakage.

 

Alternate Implementation Example – not UNIX-like:
(for a sane implementation see the source of this file.)

// a simple command shell,
// toggeling "-" (minus) and "/" (forward slash) as the option character

var term, parser;
var files = {
   "initscreen": [
      "*** Quick and Dumb Shell v. 1.0 ***",
      "Nanofluffy Corporation 1982",
      "ready.",
      "type 'help' for a list of commands."
    ],
   "help": [
       "Commands:",
       "  type [-p] <filename> .... write a file to the screen",
       "                            option p: use screen pager",
       "  print <filename> [/p] ... write a file to the screen",
       "                            option p: use screen pager",
       "  help .................... display this screen",
       "  exit .................... exit the command shell"
    ],
    "test": [
       "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nam",
       "facilisis enim. Pellentesque in elit et lacus euismod dignissim.",
       "Aliquam dolor pede, convallis eget, dictum a, blandit ac, urna.",
       "Pellentesque sed nunc ut justo volutpat egestas. Class aptent",
       "taciti sociosqu ad litora torquent per conubia nostra, per inceptos",
       "hymenaeos. In erat. Suspendisse potenti. Fusce faucibus nibh sed",
       "nisi. Phasellus faucibus, dui a cursus dapibus, mauris nulla",
       "euismod velit, a lobortis turpis arcu vel dui. Pellentesque",
       "fermentum ultrices pede. Donec auctor lectus eu arcu. Curabitur",
       "non orci eget est porta gravida. Aliquam pretium orci id nisi.",
       "Duis faucibus, mi non adipiscing venenatis, erat urna aliquet",
       "elit, eu fringilla lacus tellus quis erat. Nam tempus ornare",
       "lorem. Nullam feugiat. Lorem ipsum dolor sit amet, consectetuer",
       "adipiscing elit. Nam facilisis enim. Pellentesque in elit et lacus",
       "euismod dignissim. Aliquam dolor pede, convallis eget, dictum a,",
       "blandit ac, urna. Pellentesque sed nunc ut justo volutpat egestas.",
       "Class aptent taciti sociosqu ad litora torquent per conubia nostra,",
       "per inceptos hymenaeos. In erat. Suspendisse potenti. Fusce",
       "faucibus nibh sed nisi. Phasellus faucibus, dui a cursus dapibus,",
       "mauris nulla euismod velit, a lobortis turpis arcu vel dui.",
       "Pellentesque fermentum ultrices pede. Donec auctor lectus eu arcu.",
       "Curabitur non orci eget est porta gravida. Aliquam pretium orci id",
       "nisi. Duis faucibus, mi non adipiscing venenatis, erat urna aliquet",
       "elit, eu fringilla lacus tellus quis erat. Nam tempus ornare lorem.",
       "Nullam feugiat. Lorem ipsum dolor sit amet, consectetuer adipiscing",
       "elit. Nam facilisis enim. Pellentesque in elit et lacus euismod",
       "dignissim. Aliquam dolor pede, convallis eget, dictum a, blandit ac,",
       "urna. Pellentesque sed nunc ut justo volutpat egestas. Class aptent",
       "taciti sociosqu ad litora torquent per conubia nostra, per inceptos",
       "hymenaeos. In erat. Suspendisse potenti. Fusce faucibus nibh sed nisi.",
       "Phasellus faucibus, dui a cursus dapibus, mauris nulla euismod velit,",
       "a lobortis dui: The quick brown fox jumps over the lazy dog."
    ]
};

function openTerminal() {
   if (!term || term.closed) {
      // create and open a terminal
      term = new Terminal(
         {
            handler: commandShell,
            termDiv: "termDiv",
            ps: ">",
            greeting: files.initscreen
         }
      );
      term.open();
      
      // create and configure the parser
      parser = new Parser();
      // only accept double and single quotes as quote characters
      parser.quoteChars = { "\"": true, "'": true };
      // set "-" (minus) as the option character (same as default)
      parser.optionChars = { "-": true };
   }
}

function commandShell() {
   this.newLine();
   parser.parseLine(this);
   if (this.argv.length == 0) {
      // no commmand line input
   }
   else {
      var cmd = this.argv[this.argc++]; // we advance argc to point to the next chunk
      cmd = cmd.toLowerCase();          // case insensitive (dumb shell)
      
      // now handle each command
      switch (cmd) {
         // "system" commands
         case "help":
            // display a help screen
            this.clear();
            this.write(files.help);
            break;
         case "exit":
            // just quit with grace
            this.close();
            return;

         // implement a command "type [-p] <filename>"
         case "type":
            // read option "p"
            var opts = parser.getopt(this, "p");
            if (opts.illegals.length) {
               // other option flags found
               this.write("Error: Illegal option.");
            }
            else if (this.argc != this.argv.length-1) {
               this.write("Error: Illegal argument list.");
            }
            else {
               var filename = this.argv[this.argc];
               var file = files[filename];
               if (file) {
                  if (opts.p) {
                     // write in more-mode
                     this.write(file, true);
                     return;
                  }
                  else {
                     this.write(file);
                  }
               }
               else {
                   this.write("Error: No such file '"+filename+"'.");
               }
            }
            break;

         // implement a command "print <filename> [/p]"
         case "print":
            if (this.argv.length>this.argc) {
               var filename = this.argv[this.argc];
               if (this.argQL[this.argc] || filename.charAt(0) != "/") {
                  // filename quoted or not an option => suppose it's legal
                  // advance argc
                  this.argc++;
                  // get option /p
                  // overwrite default optionChars with forward slash
                  parser.optionChars = { "/": true };
                  var opts = parser.getopt(this, "p");
                  // now reset the optionChars
                  parser.optionChars = { "-": true };
                  // check and execute ...
                  if (opts.illegals.length) {
                     this.write("Error: Illegal option.");
                  }
                  else {
                     var file = files[filename];
                     if (file) {
                        if (opts.p) {
                           this.write(file, true);
                           return;
                        }
                        else {
                           this.write(file);
                        }
                     }
                     else {
                        this.write("Error: No such file '"+filename+"'.");
                     }
                  }
               }
               else {
                  this.write("Error: Illegal argument list.");
               }
            }
            else {
               this.write("Error: Missing argument list.");
            }
            break;

         // not a known command
         default:
            this.write("Not a command: "+cmd);
      }
   }
   this.prompt();
}

// eof