API Docs for: 0.0.1
Show:

File: lib/parsers/string.js

/*
* niViz -- snow profiles visualization
* Copyright (C) 2015 WSL/SLF - Fluelastrasse 11 - 7260 Davos Dorf - Switzerland.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

(function (niviz) {
  'use strict';

  // --- Module Dependencies ---
  var properties = Object.defineProperties;

  var Parser     = niviz.Parser;
  var util       = niviz.util;
  var format     = util.format;


  /** @module niviz */

  /**
   * A simple string/stream parser.
   *
   * @class StringParser
   * @constructor
   *
   * @extends Parser
   *
   * @param {String} [data=''] The data to parse.
   * @param {String} [delimiter='\n'] The token delimiter.
   * @param {String} [trim='\r'] The token trim value.
   */
  function StringParser(data, delimiter, trim) {
    Parser.call(this, data || '');

    /**
     * The token delimiter; defaults to a newline.
     *
     * @property delimiter
     * @type String
     */
    this.delimiter = delimiter || '\n';

    /**
     * The token trim value; defaults to a carriage
     * return. If present, this value will be cut off
     * at the end of each token during parsing.
     *
     * @property trim
     * @type String
     */
    this.trim = trim || '\r';

    /**
     * The current position in the input data.
     *
     * @property position
     * @type Number
     * @private
     */
    this.position = 0;

    /**
     * The number of lines/tokens parsed so far.
     *
     * @property lines
     * @type Number
     * @private
     */
    this.lines = 0;

  }

  Parser.implement(StringParser, 'string');


  properties(StringParser.prototype, {
    /**
     * Whether or not all data has been parsed.
     *
     * @property done
     * @type Boolean
     */
    done: {
      get: function () {
        return this.position >= this.data.length;
      }
    }
  });

  /**
   * Returns the next line/token; updates the parser's current
   * position pointer and line number as a side effect.
   *
   * @method next
   * @returns {String|undefined} The next token.
   */
  StringParser.prototype.next = function () {
    if (this.done) return undefined;

    var idx = this.data.indexOf(this.delimiter, this.position);
    if (idx < 0) idx = this.data.length;

    var token = this.data.substring(this.position, idx);
    token = trimr(token, this.trim);

    this.position = idx + this.delimiter.length;
    this.lines++;

    return token;
  };

  /**
   * @method peek
   * @returns {String} The next k characters.
   */
  StringParser.prototype.peek = function (k) {
    return this.data.substr(this.position, k || 1);
  };

  StringParser.prototype.error = function () {
    var message  = format(Array.prototype.slice.apply(arguments));
    var template = 'parser error at %d:0: %s';

    return new Error(format(template, this.lines, message));
  };

  /**
   * Like `next()` but skips lines matching `over`
   * until the end of data is reached or, if given,
   * the next line starts with `until`.
   *
   * @method skip
   *
   * @param {RegExp} over Lines to skip.
   * @param {String} [until]
   *
   * @returns {String|undefined} The next line.
   */
  StringParser.prototype.skip = function (over, until) {
    var next;

    do {
      next = (until && this.check(until)) ?
        undefined : this.next();

    } while (next !== undefined && over.test(next));

    return next;
  };

  /**
   * Returns true if the next line starts with the
   * given string `against`.
   *
   * @method check
   *
   * @param {String} against
   *
   * @returns {Boolean}
   */
  StringParser.prototype.check = function (against) {
    return this.peek(against.length) === against;
  };

  /**
   * @property _parse
   * @returns {Object} The parse result.
   */
  StringParser.prototype._parse = function () {
    var i = 0;

    try {
      while (!this.done) {
        if (i++ > 500) { // pause after 500 lines
          this.pause();
          return this.result;
        }

        this.parse_line(this.next());
      }

    } catch (error) {
      error.message = 'Line ' + this.lines + ': ' + error.message;
      return this.emit('error', error);
    }

    try {
      this.verify();
    } catch (error) {
      error.message = 'Verification error: ' + error.message;
      return this.emit('error', error);
    }

    this.end();
    return this.result;
  };

  /**
   * Resumes _parse method after a short timeout; these
   * small timeouts effectively give the browser more time
   * to breathe and are a remedy for the 'long running script'
   * warnings in Firefox.
   *
   * @method pause
   */
  StringParser.prototype.pause = function () {
    var that = this;

    setTimeout(function () {
      that._parse();
    }, 15);
  };

  /**
   * Parses a single line. This method should be
   * implemented by all subclasses.
   *
   * @method parse_line
   * @chainable
   *
   * @abstract
   *
   * @param {String} line The line to parse.
   */
  StringParser.prototype.parse_line = function (line) {
    throw new Error('not implemented');
  };

  // --- Helpers ---

  function trimr(string, value) {
    var k = string.length;

    if (k === 0 || string[k - 1] !== value)
      return string;

    return string.slice(0, k - 1);
  }

}(niviz));