Source: util/regex/ndn-regex-repeat-matcher.js

/**
 * Copyright (C) 2017-2018 Regents of the University of California.
 * @author: Yingdi Yu <http://irl.cs.ucla.edu/~yingdi/>
 * @author: Jeff Thompson <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

/** @ignore */
var NdnRegexMatcherBase = require('./ndn-regex-matcher-base.js').NdnRegexMatcherBase;

/**
 * Create an NdnRegexRepeatMatcher.
 * @param {string} expr
 * @param {NdnRegexBackrefManager} backrefManager The back-reference manager.
 * @param {number} indicator
 * @constructor
 */
var NdnRegexRepeatMatcher = function NdnRegexRepeatMatcher
  (expr, backrefManager, indicator)
{
  // Call the base constructor.
  NdnRegexMatcherBase.call
    (this, expr, NdnRegexMatcherBase.NdnRegexExprType.REPEAT_PATTERN,
     backrefManager);
  this.repeatMin_ = 0;
  this.repeatMax_ = 0;

  this.indicator_ = indicator;

  this.compile_();
};

NdnRegexRepeatMatcher.prototype = new NdnRegexMatcherBase();
NdnRegexRepeatMatcher.prototype.name = "NdnRegexRepeatMatcher";

exports.NdnRegexRepeatMatcher = NdnRegexRepeatMatcher;

/**
 * @param {Name} name
 * @param {number} offset
 * @param {number} len
 * @return {boolean}
 */
NdnRegexRepeatMatcher.prototype.match = function(name, offset, len)
{
  this.matchResult_ = [];

  if (0 === this.repeatMin_) {
    if (0 === len)
      return true;
  }

  if (this.recursiveMatch2_(0, name, offset, len)) {
    for (var i = offset; i < offset + len; ++i)
      this.matchResult_.push(name.get(i));
    return true;
  }
  else
    return false;
};

/**
 * Compile the regular expression to generate more matchers when necessary.
 */
NdnRegexRepeatMatcher.prototype.compile_ = function()
{
  var matcher;

  if ('(' == this.expr_[0]) {
    matcher = new NdnRegexBackrefMatcher
      (this.expr_.substring(0, this.indicator_), this.backrefManager_);
    this.backrefManager_.pushRef(matcher);
    matcher.lateCompile();
  }
  else
    matcher = new NdnRegexComponentSetMatcher
      (this.expr_.substring(0, this.indicator_), this.backrefManager_);

  this.matchers_.push(matcher);

  this.parseRepetition_();
};

NdnRegexRepeatMatcher.prototype.parseRepetition_ = function()
{
  var exprSize = this.expr_.length;
  var MAX_REPETITIONS = 32767;

  if (exprSize === this.indicator_) {
    this.repeatMin_ = 1;
    this.repeatMax_ = 1;

    return true;
  }
  else {
    if (exprSize === this.indicator_ + 1) {
      if ('?' == this.expr_[this.indicator_]) {
        this.repeatMin_ = 0;
        this.repeatMax_ = 1;
        return true;
      }
      if ('+' == this.expr_[this.indicator_]) {
        this.repeatMin_ = 1;
        this.repeatMax_ = MAX_REPETITIONS;
        return true;
      }
      if ('*' == this.expr_[this.indicator_]) {
        this.repeatMin_ = 0;
        this.repeatMax_ = MAX_REPETITIONS;
        return true;
      }
    }
    else {
      var repeatStruct = this.expr_.substring(this.indicator_, exprSize);
      var rsSize = repeatStruct.length;
      var min = 0;
      var max = 0;

      if (repeatStruct.match(new RegExp("\\{[0-9]+,[0-9]+\\}")) != null) {
        separator = repeatStruct.indexOf(',');
        min = parseInt(repeatStruct.substring(1, separator));
        max = parseInt(repeatStruct.substring(separator + 1, rsSize - 1));
      }
      else if (repeatStruct.match(new RegExp("\\{,[0-9]+\\}")) != null) {
        separator = repeatStruct.indexOf(',');
        min = 0;
        max = parseInt(repeatStruct.substring(separator + 1, rsSize - 1));
      }
      else if (repeatStruct.match(new RegExp("\\{[0-9]+,\\}")) != null) {
        separator = repeatStruct.indexOf(',');
        min = parseInt(repeatStruct.substring(1, separator));
        max = MAX_REPETITIONS;
      }
      else if (repeatStruct.match(new RegExp("\\{[0-9]+\\}")) != null) {
        min = parseInt(repeatStruct.substring(1, rsSize - 1));
        max = min;
      }
      else
        throw new NdnRegexMatcherBase.Error(new Error
          ("Error: RegexRepeatMatcher.ParseRepetition(): Unrecognized format " +
           this.expr_));

      if (min > MAX_REPETITIONS || max > MAX_REPETITIONS || min > max)
        throw new NdnRegexMatcherBase.Error(new Error
          ("Error: RegexRepeatMatcher.ParseRepetition(): Wrong number " +
           this.expr_));

      this.repeatMin_ = min;
      this.repeatMax_ = max;

      return true;
    }
  }

  return false;
};

/**
 *
 * @param {number} repeat
 * @param {Name} name
 * @param {number} offset
 * @param {number} len
 * @return {boolean}
 */
NdnRegexRepeatMatcher.prototype.recursiveMatch2_ = function
  (repeat, name, offset, len)
{
  var tried = len;
  var matcher = this.matchers_[0];

  if (0 < len && repeat >= this.repeatMax_)
    return false;

  if (0 === len && repeat < this.repeatMin_)
    return false;

  if (0 == len && repeat >= this.repeatMin_)
    return true;

  while (tried >= 0) {
    if (matcher.match(name, offset, tried) &&
        this.recursiveMatch2_(repeat + 1, name, offset + tried,
                              len - tried))
      return true;
    --tried;
  }

  return false;
};

// Put these last to avoid a require loop.
/** @ignore */
var NdnRegexBackrefMatcher = require('./ndn-regex-backref-matcher.js').NdnRegexBackrefMatcher; /** @ignore */
var NdnRegexComponentSetMatcher = require('./ndn-regex-component-set-matcher.js').NdnRegexComponentSetMatcher;