<commit_before>define(['backbone', 'views/header', 'views/row', 'views/pagination', 'models/pager', 'models/sorter'], function(Backbone, Header, Row, Pagination, Pager, Sorter) {

  var Datagrid = Backbone.View.extend({
    initialize: function() {
      this.columns = this.options.columns;
      this.options = _.defaults(this.options, {
        paginated: false,
        page:      1,
        perPage:   10
      });

      this.collection.on('reset', this.render, this);
      this._prepare();
    },

    render: function() {
      this.$el.empty();
      this.renderTable();
      if (this.options.paginated) {
        this.renderPagination();
      }

      return this;
    },

    renderTable: function() {
      var $table = $('

', {'class': 'table'});
      this.$el.append($table);

      var header = new Header({columns: this.columns, sorter: this.sorter});
      $table.append(header.render().el);

      $table.append('

');

      this.collection.forEach(this.renderRow, this);
    },

    renderPagination: function() {
      var pagination = new Pagination({pager: this.pager});
      this.$el.append(pagination.render().el);
    },

    renderRow: function(model) {
      var options = {
        model: model,
        columns: this.columns
      };
      var rowClassName = this.options.rowClassName;
      if (_.isFunction(rowClassName)) {
        rowClassName = rowClassName(model);
      }
      options.className = rowClassName;

      var row = new Row(options);
      this.$('tbody').append(row.render(this.columns).el);
    },

    sort: function(column, order) {
      this.sorter.sort(column, order);
    },

    page: function(page) {
      this.pager.page(page);
    },

    perPage: function(perPage) {
      this.pager.set('perPage', perPage);
    },

    _sort: function(column, order) {
      if (this.options.inMemory) {
        this._sortInMemory(column, order);
      } else {
        this._sortRequest(column, order);
      }
    },

    _sortInMemory: function(column, order) {
      this.collection.comparator = function(model) {
        return model.get(column);
      };
      this.collection.sort();
    },

    _sortRequest: function(column, order) {
    },

    _page: function(page, options) {
      if (this.options.inMemory) {
        this._pageInMemory(page, options);
      } else {
        this._pageRequest(page, options);
      }
    },

    _pageRequest: function(page, options) {
      this.collection.fetch(options);
    },

    _pageInMemory: function(page, options) {
      if (!this._originalCollection) {
        this._originalCollection = this.collection.clone();
      }

      var perPage = this.pager.get('perPage');

      var begin = (page - 1) * perPage;
      var end   = begin + perPage;

      this.collection.reset(this._originalCollection.slice(begin, end), options);
      this.pager.set('total', this._originalCollection.size());
    },

    _prepare: function() {
      this._prepareColumns();
      this._prepareSorter();
      if (this.options.paginated) {
        this._preparePager();
        this._page(this.options.page, {silent: true});
      }
    },

    _prepareSorter: function() {
      this.sorter = new Sorter();
      this.sorter.on('change', function() {
        this._sort(this.sorter.get('column'));
      }, this);
    },

    _preparePager: function() {
      this.pager = new Pager({
        currentPage: this.options.page,
        perPage:     this.options.perPage,
        total:       this.collection.size()
      });

      this.pager.on('change:currentPage', function() {
        this._page(this.pager.get('currentPage'));
      }, this);
      this.pager.on('change:perPage', function() {
        this.page(1);
        // manually trigger this event even if we already are on page 1
        this.pager.trigger('change:currentPage');
      }, this);
    },

    _prepareColumns: function() {
      if (!this.columns || _.isEmpty(this.columns)) {
        this._defaultColumns();
      } else {
        _.each(this.columns, function(column, i) {
          this.columns[i] = this._prepareColumn(column);
        }, this);
      }
    },

    _prepareColumn: function(column) {
      if (_.isString(column)) {
        column = { property: column };
      }
      if (_.isObject(column)) {
        column.title = column.title || column.property.charAt(0).toUpperCase() + column.property.substr(1);
      }
      return column;
    },

    _defaultColumns: function() {
      this.columns = [];
      var model = this.collection.first();
      if (model) {
        for (var p in model.toJSON()) {
          this.columns.push(this._prepareColumn(p));
        }
      }
    }
  });

  return Datagrid;

});
<commit_msg>Very basic reverse sorting.<commit_after>define(['backbone', 'views/header', 'views/row', 'views/pagination', 'models/pager', 'models/sorter'], function(Backbone, Header, Row, Pagination, Pager, Sorter) {

  var Datagrid = Backbone.View.extend({
    initialize: function() {
      this.columns = this.options.columns;
      this.options = _.defaults(this.options, {
        paginated: false,
        page:      1,
        perPage:   10
      });

      this.collection.on('reset', this.render, this);
      this._prepare();
    },

    render: function() {
      this.$el.empty();
      this.renderTable();
      if (this.options.paginated) {
        this.renderPagination();
      }

      return this;
    },

    renderTable: function() {
      var $table = $('

', {'class': 'table'});
      this.$el.append($table);

      var header = new Header({columns: this.columns, sorter: this.sorter});
      $table.append(header.render().el);

      $table.append('

');

      this.collection.forEach(this.renderRow, this);
    },

    renderPagination: function() {
      var pagination = new Pagination({pager: this.pager});
      this.$el.append(pagination.render().el);
    },

    renderRow: function(model) {
      var options = {
        model: model,
        columns: this.columns
      };
      var rowClassName = this.options.rowClassName;
      if (_.isFunction(rowClassName)) {
        rowClassName = rowClassName(model);
      }
      options.className = rowClassName;

      var row = new Row(options);
      this.$('tbody').append(row.render(this.columns).el);
    },

    sort: function(column, order) {
      this.sorter.sort(column, order);
    },

    page: function(page) {
      this.pager.page(page);
    },

    perPage: function(perPage) {
      this.pager.set('perPage', perPage);
    },

    _sort: function(column, order) {
      if (this.options.inMemory) {
        this._sortInMemory(column, order);
      } else {
        this._sortRequest(column, order);
      }
    },

    _sortInMemory: function(column, order) {
      this.collection.comparator = function(model) {
        return model.get(column);
      };
      this.collection.sort({silent: true});
      if (order === Sorter.DESC) {
        this.collection.models.reverse();
      }
      this.collection.trigger('reset');
    },

    _sortRequest: function(column, order) {
    },

    _page: function(page, options) {
      if (this.options.inMemory) {
        this._pageInMemory(page, options);
      } else {
        this._pageRequest(page, options);
      }
    },

    _pageRequest: function(page, options) {
      this.collection.fetch(options);
    },

    _pageInMemory: function(page, options) {
      if (!this._originalCollection) {
        this._originalCollection = this.collection.clone();
      }

      var perPage = this.pager.get('perPage');

      var begin = (page - 1) * perPage;
      var end   = begin + perPage;

      this.collection.reset(this._originalCollection.slice(begin, end), options);
      this.pager.set('total', this._originalCollection.size());
    },

    _prepare: function() {
      this._prepareColumns();
      this._prepareSorter();
      if (this.options.paginated) {
        this._preparePager();
        this._page(this.options.page, {silent: true});
      }
    },

    _prepareSorter: function() {
      this.sorter = new Sorter();
      this.sorter.on('change', function() {
        this._sort(this.sorter.get('column'), this.sorter.get('order'));
      }, this);
    },

    _preparePager: function() {
      this.pager = new Pager({
        currentPage: this.options.page,
        perPage:     this.options.perPage,
        total:       this.collection.size()
      });

      this.pager.on('change:currentPage', function() {
        this._page(this.pager.get('currentPage'));
      }, this);
      this.pager.on('change:perPage', function() {
        this.page(1);
        // manually trigger this event even if we already are on page 1
        this.pager.trigger('change:currentPage');
      }, this);
    },

    _prepareColumns: function() {
      if (!this.columns || _.isEmpty(this.columns)) {
        this._defaultColumns();
      } else {
        _.each(this.columns, function(column, i) {
          this.columns[i] = this._prepareColumn(column);
        }, this);
      }
    },

    _prepareColumn: function(column) {
      if (_.isString(column)) {
        column = { property: column };
      }
      if (_.isObject(column)) {
        column.title = column.title || column.property.charAt(0).toUpperCase() + column.property.substr(1);
      }
      return column;
    },

    _defaultColumns: function() {
      this.columns = [];
      var model = this.collection.first();
      if (model) {
        for (var p in model.toJSON()) {
          this.columns.push(this._prepareColumn(p));
        }
      }
    }
  });

  return Datagrid;

});
<|endoftext|>


<commit_before>'use strict'

var StringDecoder = require('string_decoder').StringDecoder
var decoder = new StringDecoder()
var ReplyError = require('./replyError')
var bufferPool = new Buffer(32 * 1024)
var bufferOffset = 0
var interval = null
var counter = 0
var notDecreased = 0

/**
 * Used for lengths and numbers only, faster perf on arrays / bulks
 * @param parser
 * @returns {*}
 */
function parseSimpleNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length
  var number = 0
  var sign = false

  if (parser.buffer[offset] === 45) {
    sign = true
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13 && parser.buffer[offset] === 10) { // \r\n
      parser.offset = offset + 1
      return sign ? -number : number
    }
    number = (number * 10) + (c1 - 48)
  }
}

/**
 * Used for integer numbers in case of the returnNumbers option
 *
 * The maximimum possible integer to use is: Math.floor(Number.MAX_SAFE_INTEGER / 10) - 9
 *
 * @param parser
 * @returns {*}
 */
function parseStringNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length - 1
  var number = 0
  var res = ''

  if (parser.buffer[offset] === 45) {
    res += '-'
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13) { // \r\n
      parser.offset = offset + 1
      if (number !== 0) {
        res += number
      }
      return res
    } else if (number > 900719925474090) {
      res += number
      res += c1 - 48
      number = 0
    } else if (c1 === 48 && number === 0) {
      res += 0
    } else {
      number = (number * 10) + (c1 - 48)
    }
  }
}

/**
 * Returns a string or buffer of the provided offset start and
 * end ranges. Checks `optionReturnBuffers`.
 *
 * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
 *
 * @param parser
 * @param start
 * @param end
 * @returns {*}
 */
function convertBufferRange (parser, start, end) {
  parser.offset = end + 2
  if (parser.optionReturnBuffers === true) {
    return parser.buffer.slice(start, end)
  }

  return parser.buffer.toString('utf-8', start, end)
}

/**
 * Parse a '+' redis simple string response but forward the offsets
 * onto convertBufferRange to generate a string.
 * @param parser
 * @returns {*}
 */
function parseSimpleStringViaOffset (parser) {
  var start = parser.offset
  var offset = parser.offset
  var length = parser.buffer.length
  var buffer = parser.buffer

  while (offset < length) {
    if (buffer[offset++] === 10) { // \r\n
      return convertBufferRange(parser, start, offset - 2)
    }
  }
}

/**
 * Returns the string length via parseSimpleNumbers
 * @param parser
 * @returns {*}
 */
function parseLength (parser) {
  var string = parseSimpleNumbers(parser)
  if (string !== undefined) {
    return +string
  }
}

/**
 * Parse a ':' redis integer response
 *
 * If stringNumbers is activated the parser always returns numbers as string
 * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
 * are 64bit floating point numbers with reduced precision
 *
 * @param parser
 * @returns {*}
 */
function parseInteger (parser) {
  if (parser.optionStringNumbers) {
    return parseStringNumbers(parser)
  }
  return parseSimpleNumbers(parser)
}

/**
 * Parse a '$' redis bulk string response
 * @param parser
 * @returns {*}
 */
function parseBulkString (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }
  var offsetEnd = parser.offset + length
  if (offsetEnd + 2 > parser.buffer.length) {
    parser.bigStrSize = offsetEnd + 2
    parser.bigOffset = parser.offset
    parser.totalChunkSize = parser.buffer.length
    parser.bufferCache.push(parser.buffer)
    return
  }

  return convertBufferRange(parser, parser.offset, offsetEnd)
}

/**
 * Parse a '-' redis error response
 * @param parser
 * @returns {Error}
 */
function parseError (parser) {
  var string = parseSimpleStringViaOffset(parser)
  if (string !== undefined) {
    if (parser.optionReturnBuffers === true) {
      string = string.toString()
    }
    return new ReplyError(string)
  }
}

/**
 * Parsing error handler, resets parser buffer
 * @param parser
 * @param error
 */
function handleError (parser, error) {
  parser.buffer = null
  parser.returnFatalError(error)
}

/**
 * Parse a '*' redis array response
 * @param parser
 * @returns {*}
 */
function parseArray (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }

  var responses = new Array(length)
  var bufferLength = parser.buffer.length
  for (var i = 0; i < length; i++) {
    if (parser.offset >= bufferLength) {
      return
    }
    var response = parseType(parser, parser.buffer[parser.offset++])
    if (response === undefined) {
      return
    }
    responses[i] = response
  }

  return responses
}

/**
 * Called the appropriate parser for the specified type.
 * @param parser
 * @param type
 * @returns {*}
 */
function parseType (parser, type) {
  switch (type) {
    case 36: // $
      return parseBulkString(parser)
    case 58: // :
      return parseInteger(parser)
    case 43: // +
      return parseSimpleStringViaOffset(parser)
    case 42: // *
      return parseArray(parser)
    case 45: // -
      return parseError(parser)
    default:
      var err = new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte', 20)
      err.offset = parser.offset
      err.buffer = JSON.stringify(parser.buffer)
      return handleError(parser, err)
  }
}

// All allowed options including their typeof value
var optionTypes = {
  returnError: 'function',
  returnFatalError: 'function',
  returnReply: 'function',
  returnBuffers: 'boolean',
  stringNumbers: 'boolean',
  name: 'string'
}

/**
 * Javascript Redis Parser
 * @param options
 * @constructor
 */
function JavascriptRedisParser (options) {
  if (!(this instanceof JavascriptRedisParser)) {
    return new JavascriptRedisParser(options)
  }
  if (!options || !options.returnError || !options.returnReply) {
    throw new TypeError('Please provide all return functions while initiating the parser')
  }
  for (var key in options) {
    if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
      throw new TypeError('The options argument contains the property "' + key + '" that is either unkown or of a wrong type')
    }
  }
  if (options.name === 'hiredis') {
    /* istanbul ignore next: hiredis is only supported for legacy usage */
    try {
      var Hiredis = require('./hiredis')
      console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
      return new Hiredis(options)
    } catch (e) {
      console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
    }
  }
  this.optionReturnBuffers = !!options.returnBuffers
  this.optionStringNumbers = !!options.stringNumbers
  this.returnError = options.returnError
  this.returnFatalError = options.returnFatalError || options.returnError
  this.returnReply = options.returnReply
  this.name = 'javascript'
  this.offset = 0
  this.buffer = null
  this.bigStrSize = 0
  this.bigOffset = 0
  this.totalChunkSize = 0
  this.bufferCache = []
}

/**
 * Concat a bulk string containing multiple chunks
 *
 * Notes:
 * 1) The first chunk might contain the whole bulk string including the \r
 * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
 *
 * @param parser
 * @param buffer
 * @returns {String}
 */
function concatBulkString (parser) {
  var list = parser.bufferCache
  var chunks = list.length
  var offset = parser.bigStrSize - parser.totalChunkSize
  parser.offset = offset
  if (offset === 1) {
    if (chunks === 2) {
      return list[0].toString('utf8', parser.bigOffset, list[0].length - 1)
    }
    chunks--
    offset = list[list.length - 2].length + 1
  }
  var res = decoder.write(list[0].slice(parser.bigOffset))
  for (var i = 1; i < chunks - 1; i++) {
    res += decoder.write(list[i])
  }
  res += decoder.end(list[i].slice(0, offset - 2))
  return res
}

/**
 * Decrease the bufferPool size over time
 * @returns {undefined}
 */
function decreaseBufferPool () {
  if (bufferPool.length > 50 * 1024) {
    // Balance between increasing and decreasing the bufferPool
    if (counter === 1 || notDecreased > counter * 2) {
      // Decrease the bufferPool by 10% by removing the first 10% of the current pool
      var sliceLength = Math.floor(bufferPool.length / 10)
      if (bufferOffset <= sliceLength) {
        bufferOffset = 0
      } else {
        bufferOffset -= sliceLength
      }
      bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
    } else {
      notDecreased++
      counter--
    }
  } else {
    clearInterval(interval)
    counter = 0
    notDecreased = 0
    interval = null
  }
}

/**
 * Concat the collected chunks from parser.bufferCache
 * @param parser
 * @param length
 * @returns {Buffer}
 */
function concatBuffer (parser, length) {
  var list = parser.bufferCache
  var pos = bufferOffset
  length -= parser.offset
  if (bufferPool.length < length + bufferOffset) {
    // Increase the bufferPool size
    var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
    if (bufferOffset > 1024 * 1024 * 120) {
      bufferOffset = 1024 * 1024 * 50
    }
    bufferPool = new Buffer(length * multiplier + bufferOffset)
    bufferOffset = 0
    counter++
    pos = 0
    if (interval === null) {
      interval = setInterval(decreaseBufferPool, 50).unref()
    }
  }
  list[0].copy(bufferPool, pos, parser.offset, list[0].length)
  pos += list[0].length - parser.offset
  for (var i = 1; i < list.length; i++) {
    list[i].copy(bufferPool, pos)
    pos += list[i].length
  }
  var buffer = bufferPool.slice(bufferOffset, length + bufferOffset)
  bufferOffset += length
  return buffer
}

/**
 * Parse the redis buffer
 * @param buffer
 * @returns {undefined}
 */
JavascriptRedisParser.prototype.execute = function execute (buffer) {
  if (this.buffer === null) {
    this.buffer = buffer
    this.offset = 0
  } else if (this.bigStrSize === 0) {
    var oldLength = this.buffer.length
    var remainingLength = oldLength - this.offset
    var newBuffer = new Buffer(remainingLength + buffer.length)
    this.buffer.copy(newBuffer, 0, this.offset, oldLength)
    buffer.copy(newBuffer, remainingLength, 0, buffer.length)
    this.buffer = newBuffer
    this.offset = 0
  } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
    this.bufferCache.push(buffer)
    // The returned type might be Array * (42) and in that case we can't improve the parsing currently
    if (this.optionReturnBuffers === false && this.buffer[this.offset] === 36) {
      this.returnReply(concatBulkString(this))
      this.buffer = buffer
    } else { // This applies for arrays too
      this.buffer = concatBuffer(this, this.totalChunkSize + buffer.length)
      this.offset = 0
    }
    this.bigStrSize = 0
    this.totalChunkSize = 0
    this.bufferCache = []
  } else {
    this.bufferCache.push(buffer)
    this.totalChunkSize += buffer.length
    return
  }

  while (this.offset < this.buffer.length) {
    var offset = this.offset
    var type = this.buffer[this.offset++]
    var response = parseType(this, type)
    if (response === undefined) {
      this.offset = offset
      return
    }

    if (type === 45) {
      this.returnError(response)
    } else {
      this.returnReply(response)
    }
  }

  this.buffer = null
}

module.exports = JavascriptRedisParser
<commit_msg>Improve simple string and number performance by 5-15%<commit_after>'use strict'

var StringDecoder = require('string_decoder').StringDecoder
var decoder = new StringDecoder()
var ReplyError = require('./replyError')
var bufferPool = new Buffer(32 * 1024)
var bufferOffset = 0
var interval = null
var counter = 0
var notDecreased = 0

/**
 * Used for lengths and numbers only, faster perf on arrays / bulks
 * @param parser
 * @returns {*}
 */
function parseSimpleNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length - 1
  var number = 0
  var sign = 1

  if (parser.buffer[offset] === 45) {
    sign = -1
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13) { // \r\n
      parser.offset = offset + 1
      return sign * number
    }
    number = (number * 10) + (c1 - 48)
  }
}

/**
 * Used for integer numbers in case of the returnNumbers option
 *
 * The maximimum possible integer to use is: Math.floor(Number.MAX_SAFE_INTEGER / 10) - 9
 *
 * @param parser
 * @returns {*}
 */
function parseStringNumbers (parser) {
  var offset = parser.offset
  var length = parser.buffer.length - 1
  var number = 0
  var res = ''

  if (parser.buffer[offset] === 45) {
    res += '-'
    offset++
  }

  while (offset < length) {
    var c1 = parser.buffer[offset++]
    if (c1 === 13) { // \r\n
      parser.offset = offset + 1
      if (number !== 0) {
        res += number
      }
      return res
    } else if (number > 900719925474090) {
      res += number
      res += c1 - 48
      number = 0
    } else if (c1 === 48 && number === 0) {
      res += 0
    } else {
      number = (number * 10) + (c1 - 48)
    }
  }
}

/**
 * Returns a string or buffer of the provided offset start and
 * end ranges. Checks `optionReturnBuffers`.
 *
 * If returnBuffers is active, all return values are returned as buffers besides numbers and errors
 *
 * @param parser
 * @param start
 * @param end
 * @returns {*}
 */
function convertBufferRange (parser, start, end) {
  parser.offset = end + 2
  if (parser.optionReturnBuffers === true) {
    return parser.buffer.slice(start, end)
  }

  return parser.buffer.toString('utf-8', start, end)
}

/**
 * Parse a '+' redis simple string response but forward the offsets
 * onto convertBufferRange to generate a string.
 * @param parser
 * @returns {*}
 */
function parseSimpleStringViaOffset (parser) {
  var start = parser.offset
  var offset = parser.offset
  var length = parser.buffer.length - 1
  var buffer = parser.buffer

  while (offset < length) {
    if (buffer[offset++] === 13) { // \r\n
      return convertBufferRange(parser, start, offset - 1)
    }
  }
}

/**
 * Returns the string length via parseSimpleNumbers
 * @param parser
 * @returns {*}
 */
function parseLength (parser) {
  var string = parseSimpleNumbers(parser)
  if (string !== undefined) {
    return +string
  }
}

/**
 * Parse a ':' redis integer response
 *
 * If stringNumbers is activated the parser always returns numbers as string
 * This is important for big numbers (number > Math.pow(2, 53)) as js numbers
 * are 64bit floating point numbers with reduced precision
 *
 * @param parser
 * @returns {*}
 */
function parseInteger (parser) {
  if (parser.optionStringNumbers) {
    return parseStringNumbers(parser)
  }
  return parseSimpleNumbers(parser)
}

/**
 * Parse a '$' redis bulk string response
 * @param parser
 * @returns {*}
 */
function parseBulkString (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }
  var offsetEnd = parser.offset + length
  if (offsetEnd + 2 > parser.buffer.length) {
    parser.bigStrSize = offsetEnd + 2
    parser.bigOffset = parser.offset
    parser.totalChunkSize = parser.buffer.length
    parser.bufferCache.push(parser.buffer)
    return
  }

  return convertBufferRange(parser, parser.offset, offsetEnd)
}

/**
 * Parse a '-' redis error response
 * @param parser
 * @returns {Error}
 */
function parseError (parser) {
  var string = parseSimpleStringViaOffset(parser)
  if (string !== undefined) {
    if (parser.optionReturnBuffers === true) {
      string = string.toString()
    }
    return new ReplyError(string)
  }
}

/**
 * Parsing error handler, resets parser buffer
 * @param parser
 * @param error
 */
function handleError (parser, error) {
  parser.buffer = null
  parser.returnFatalError(error)
}

/**
 * Parse a '*' redis array response
 * @param parser
 * @returns {*}
 */
function parseArray (parser) {
  var length = parseLength(parser)
  if (length === undefined) {
    return
  }
  if (length === -1) {
    return null
  }

  var responses = new Array(length)
  var bufferLength = parser.buffer.length - 3
  for (var i = 0; i < length; i++) {
    if (parser.offset >= bufferLength) {
      return
    }
    var response = parseType(parser, parser.buffer[parser.offset++])
    if (response === undefined) {
      return
    }
    responses[i] = response
  }

  return responses
}

/**
 * Called the appropriate parser for the specified type.
 * @param parser
 * @param type
 * @returns {*}
 */
function parseType (parser, type) {
  switch (type) {
    case 36: // $
      return parseBulkString(parser)
    case 58: // :
      return parseInteger(parser)
    case 43: // +
      return parseSimpleStringViaOffset(parser)
    case 42: // *
      return parseArray(parser)
    case 45: // -
      return parseError(parser)
    default:
      var err = new ReplyError('Protocol error, got ' + JSON.stringify(String.fromCharCode(type)) + ' as reply type byte', 20)
      err.offset = parser.offset
      err.buffer = JSON.stringify(parser.buffer)
      return handleError(parser, err)
  }
}

// All allowed options including their typeof value
var optionTypes = {
  returnError: 'function',
  returnFatalError: 'function',
  returnReply: 'function',
  returnBuffers: 'boolean',
  stringNumbers: 'boolean',
  name: 'string'
}

/**
 * Javascript Redis Parser
 * @param options
 * @constructor
 */
function JavascriptRedisParser (options) {
  if (!(this instanceof JavascriptRedisParser)) {
    return new JavascriptRedisParser(options)
  }
  if (!options || !options.returnError || !options.returnReply) {
    throw new TypeError('Please provide all return functions while initiating the parser')
  }
  for (var key in options) {
    if (optionTypes.hasOwnProperty(key) && typeof options[key] !== optionTypes[key]) {
      throw new TypeError('The options argument contains the property "' + key + '" that is either unkown or of a wrong type')
    }
  }
  if (options.name === 'hiredis') {
    /* istanbul ignore next: hiredis is only supported for legacy usage */
    try {
      var Hiredis = require('./hiredis')
      console.error(new TypeError('Using hiredis is discouraged. Please use the faster JS parser by removing the name option.').stack.replace('Error', 'Warning'))
      return new Hiredis(options)
    } catch (e) {
      console.error(new TypeError('Hiredis is not installed. Please remove the `name` option. The (faster) JS parser is used instead.').stack.replace('Error', 'Warning'))
    }
  }
  this.optionReturnBuffers = !!options.returnBuffers
  this.optionStringNumbers = !!options.stringNumbers
  this.returnError = options.returnError
  this.returnFatalError = options.returnFatalError || options.returnError
  this.returnReply = options.returnReply
  this.name = 'javascript'
  this.offset = 0
  this.buffer = null
  this.bigStrSize = 0
  this.bigOffset = 0
  this.totalChunkSize = 0
  this.bufferCache = []
}

/**
 * Concat a bulk string containing multiple chunks
 *
 * Notes:
 * 1) The first chunk might contain the whole bulk string including the \r
 * 2) We are only safe to fully add up elements that are neither the first nor any of the last two elements
 *
 * @param parser
 * @param buffer
 * @returns {String}
 */
function concatBulkString (parser) {
  var list = parser.bufferCache
  var chunks = list.length
  var offset = parser.bigStrSize - parser.totalChunkSize
  parser.offset = offset
  if (offset === 1) {
    if (chunks === 2) {
      return list[0].toString('utf8', parser.bigOffset, list[0].length - 1)
    }
    chunks--
    offset = list[list.length - 2].length + 1
  }
  var res = decoder.write(list[0].slice(parser.bigOffset))
  for (var i = 1; i < chunks - 1; i++) {
    res += decoder.write(list[i])
  }
  res += decoder.end(list[i].slice(0, offset - 2))
  return res
}

/**
 * Decrease the bufferPool size over time
 * @returns {undefined}
 */
function decreaseBufferPool () {
  if (bufferPool.length > 50 * 1024) {
    // Balance between increasing and decreasing the bufferPool
    if (counter === 1 || notDecreased > counter * 2) {
      // Decrease the bufferPool by 10% by removing the first 10% of the current pool
      var sliceLength = Math.floor(bufferPool.length / 10)
      if (bufferOffset <= sliceLength) {
        bufferOffset = 0
      } else {
        bufferOffset -= sliceLength
      }
      bufferPool = bufferPool.slice(sliceLength, bufferPool.length)
    } else {
      notDecreased++
      counter--
    }
  } else {
    clearInterval(interval)
    counter = 0
    notDecreased = 0
    interval = null
  }
}

/**
 * Concat the collected chunks from parser.bufferCache
 * @param parser
 * @param length
 * @returns {Buffer}
 */
function concatBuffer (parser, length) {
  var list = parser.bufferCache
  var pos = bufferOffset
  length -= parser.offset
  if (bufferPool.length < length + bufferOffset) {
    // Increase the bufferPool size
    var multiplier = length > 1024 * 1024 * 75 ? 2 : 3
    if (bufferOffset > 1024 * 1024 * 120) {
      bufferOffset = 1024 * 1024 * 50
    }
    bufferPool = new Buffer(length * multiplier + bufferOffset)
    bufferOffset = 0
    counter++
    pos = 0
    if (interval === null) {
      interval = setInterval(decreaseBufferPool, 50).unref()
    }
  }
  list[0].copy(bufferPool, pos, parser.offset, list[0].length)
  pos += list[0].length - parser.offset
  for (var i = 1; i < list.length; i++) {
    list[i].copy(bufferPool, pos)
    pos += list[i].length
  }
  var buffer = bufferPool.slice(bufferOffset, length + bufferOffset)
  bufferOffset += length
  return buffer
}

/**
 * Parse the redis buffer
 * @param buffer
 * @returns {undefined}
 */
JavascriptRedisParser.prototype.execute = function execute (buffer) {
  if (this.buffer === null) {
    this.buffer = buffer
    this.offset = 0
  } else if (this.bigStrSize === 0) {
    var oldLength = this.buffer.length
    var remainingLength = oldLength - this.offset
    var newBuffer = new Buffer(remainingLength + buffer.length)
    this.buffer.copy(newBuffer, 0, this.offset, oldLength)
    buffer.copy(newBuffer, remainingLength, 0, buffer.length)
    this.buffer = newBuffer
    this.offset = 0
  } else if (this.totalChunkSize + buffer.length >= this.bigStrSize) {
    this.bufferCache.push(buffer)
    // The returned type might be Array * (42) and in that case we can't improve the parsing currently
    if (this.optionReturnBuffers === false && this.buffer[this.offset] === 36) {
      this.returnReply(concatBulkString(this))
      this.buffer = buffer
    } else { // This applies for arrays too
      this.buffer = concatBuffer(this, this.totalChunkSize + buffer.length)
      this.offset = 0
    }
    this.bigStrSize = 0
    this.totalChunkSize = 0
    this.bufferCache = []
  } else {
    this.bufferCache.push(buffer)
    this.totalChunkSize += buffer.length
    return
  }

  while (this.offset < this.buffer.length) {
    var offset = this.offset
    var type = this.buffer[this.offset++]
    var response = parseType(this, type)
    if (response === undefined) {
      this.offset = offset
      return
    }

    if (type === 45) {
      this.returnError(response)
    } else {
      this.returnReply(response)
    }
  }

  this.buffer = null
}

module.exports = JavascriptRedisParser
<|endoftext|>


<commit_before>'use strict'
L20n.Parser = function() {
  let content = null;
  let patterns = {
    id: /^([_a-zA-Z]\w*)/,
    value: /^(["'])([^'"]*)(["'])/,
    ws: /^\s+/
  }

  function parse(string) {
    let lol = {type: 'LOL',
               body: []}
    content = string
    get_ws();
    while (content) {
      lol.body.push(get_entry())
      get_ws();
    }
    return lol
  }

  function get_ws() {
    content = content.replace(patterns['ws'], '')
  }

  function get_entry() {
    let entry
    if (content[0] == '<') {
      content = content.substr(1)
      let id = get_identifier()
      if (content[0] == '(') {
        entry = get_macro(id)
      } else if (content[0] == '[') {
        let index = get_index()
        entry = get_entity(id, index)
      } else {
        entry = get_entity(id);
      }
    } else if (content.substr(0,2) == '/*') {
      entry = get_comment();
    } else if (content.substr(0,6) == 'import') {
      entry = get_importstatement()
    } else {
      throw "ParserError at get_entry"
    }
    return entry
  }

  function get_importstatement() {
    content = content.substr(6)
    get_ws()
    if (content[0] != '(') {
      throw "ParserError"
    }
    content = content.substr(1)
    get_ws()
    let uri = get_string()
    get_ws()
    if (content[0] != ')') {
      throw "ParserError"
    }
    content = content.substr(1)
    let impStmt = {
      type: 'ImportStatement',
      uri: uri
    }
    return impStmt
  }

  function get_identifier() {
    if (content[0] == '~') {
      // this expression
    }
    let match = patterns['id'].exec(content)
    if (!match)
      throw "ParserError"
    content = content.substr(match[0].length)
    let identifier = {type: 'Identifier',
                  name: match[0]}
    return identifier
  }

  function get_entity(id, index) {
    let ch = content[0]
    get_ws();
    if (content[0] == '>') {
      // empty entity
    }
    if (!/\s/g.test(ch)) {
      throw "ParserError at get_entity"
    }
    let value = get_value(true)
    get_ws()
    let attrs = get_attributes()
    let entity = {
      type: 'Entity',
      id: id,
      value: value,
      index: index || [],
      attrs: attrs,
      local: (id.name[0] == '_')
    }
    return entity
  }

  function get_value(none) {
    let c = content[0]
    let value
    if (c == '"' || c == "'") {
      let ccc = content.substr(3)
      let quote = (ccc == '"""' || ccc == "'''")?ccc:c
      //let value = get_string()
      value = get_complex_string(quote)
    }
    return value
  }

  function get_string() {
    let match = patterns['value'].exec(content)
    if (!match) {
      throw "ParserError"
    }
    content = content.substr(match[0].length)
    return {type: 'String', content: match[2]}
  }

  function get_complex_string(quote) {
    let str_end = quote[0]
    let literal = new RegExp("^([^\\\{"+str_end+"]+)")
    let obj = []
    let buffer = ''
    content = content.substr(quote.length)
    let i = 0
    while (content.substr(0, quote.length) != quote) {
      i++;
      if (i>20)
        break
      if (content[0] == str_end) {
        buffer += content[0]
        content = content.substr(1)
      }
      if (content[0] == '\\') {
        let jump = content.substr(1, 3) == '{{' ? 3 : 2;
        buffer += content.substr(1, jump)
        content = content.substr(jump)
      }
      if (content.substr(0, 2) == '{{') {
        content = content.substr(2)
        if (buffer) {
          let string = {type: 'String', content: buffer}
          obj.push(string)
          buffer = ''
        }
        get_ws()
        let expr = get_expression()
        obj.push(expr)
        if (content.substr(0, 2) != '}}') {
          throw "ParserError at get_complex_string"
        }
        content = content.substr(2)
      }
      let m = literal.exec(content)
      if (m) {
        buffer = m[1]
        content = content.substr(m[0].length)
      }
    }
    if (buffer) {
      let string = {type: 'String', content: buffer}
      obj.push(string)
    }
    content = content.substr(quote.length)
    if (obj.length == 1 && obj[0].type == 'String') {
      return obj[0]
    }
    let cs = {type: 'ComplexString', content: obj}
    return cs
  }

  function get_kvp(cl) {
    let key = get_identifier()
    get_ws()
    if (content[0] != ':') {
      throw "ParserError"
    }
    content = content.substr(1)
    get_ws()
    let val = get_value()
    let kvp = {type: cl, key: key, value: val}
    return kvp
  }

  function get_attributes() {
    if (content[0] == '>') {
      content = content.substr(1)
      return {}
    }
    let attrs = {}
    while (1) {
      let attr = get_kvp('Attribute')
      attrs[attr.key.name] = attr
      let ch = content[0]
      get_ws()
      if (content[0] == '>') {
        content = content.substr(1)
        break
      } else if (!/^\s/.test(ch)) {
        throw "ParserError"
      }
    }
    return attrs
  }

  function get_expression() {
    let exp = get_primary_expression()
    get_ws()
    return exp
  }

  function get_primary_expression() {
    if (content[0] == '$') {
      return get_variable()
    }
    return get_identifier()
  }

  function get_variable() {
    content = content.substr(1)
    let id = get_identifier()
    let ve = {type: 'VariableExpression', id: id}
    return ve
  }

  return {
    parse: parse,
  }
}
<commit_msg>add support for local attributes in parser<commit_after>'use strict'
L20n.Parser = function() {
  let content = null;
  let patterns = {
    id: /^([_a-zA-Z]\w*)/,
    value: /^(["'])([^'"]*)(["'])/,
    ws: /^\s+/
  }

  function parse(string) {
    let lol = {type: 'LOL',
               body: []}
    content = string
    get_ws();
    while (content) {
      lol.body.push(get_entry())
      get_ws();
    }
    return lol
  }

  function get_ws() {
    content = content.replace(patterns['ws'], '')
  }

  function get_entry() {
    let entry
    if (content[0] == '<') {
      content = content.substr(1)
      let id = get_identifier()
      if (content[0] == '(') {
        entry = get_macro(id)
      } else if (content[0] == '[') {
        let index = get_index()
        entry = get_entity(id, index)
      } else {
        entry = get_entity(id);
      }
    } else if (content.substr(0,2) == '/*') {
      entry = get_comment();
    } else if (content.substr(0,6) == 'import') {
      entry = get_importstatement()
    } else {
      throw "ParserError at get_entry"
    }
    return entry
  }

  function get_importstatement() {
    content = content.substr(6)
    get_ws()
    if (content[0] != '(') {
      throw "ParserError"
    }
    content = content.substr(1)
    get_ws()
    let uri = get_string()
    get_ws()
    if (content[0] != ')') {
      throw "ParserError"
    }
    content = content.substr(1)
    let impStmt = {
      type: 'ImportStatement',
      uri: uri
    }
    return impStmt
  }

  function get_identifier() {
    if (content[0] == '~') {
      // this expression
    }
    let match = patterns['id'].exec(content)
    if (!match)
      throw "ParserError"
    content = content.substr(match[0].length)
    let identifier = {type: 'Identifier',
                  name: match[0]}
    return identifier
  }

  function get_entity(id, index) {
    let ch = content[0]
    get_ws();
    if (content[0] == '>') {
      // empty entity
    }
    if (!/\s/g.test(ch)) {
      throw "ParserError at get_entity"
    }
    let value = get_value(true)
    get_ws()
    let attrs = get_attributes()
    let entity = {
      type: 'Entity',
      id: id,
      value: value,
      index: index || [],
      attrs: attrs,
      local: (id.name[0] == '_')
    }
    return entity
  }

  function get_value(none) {
    let c = content[0]
    let value
    if (c == '"' || c == "'") {
      let ccc = content.substr(3)
      let quote = (ccc == '"""' || ccc == "'''")?ccc:c
      //let value = get_string()
      value = get_complex_string(quote)
    }
    return value
  }

  function get_string() {
    let match = patterns['value'].exec(content)
    if (!match) {
      throw "ParserError"
    }
    content = content.substr(match[0].length)
    return {type: 'String', content: match[2]}
  }

  function get_complex_string(quote) {
    let str_end = quote[0]
    let literal = new RegExp("^([^\\\{"+str_end+"]+)")
    let obj = []
    let buffer = ''
    content = content.substr(quote.length)
    let i = 0
    while (content.substr(0, quote.length) != quote) {
      i++;
      if (i>20)
        break
      if (content[0] == str_end) {
        buffer += content[0]
        content = content.substr(1)
      }
      if (content[0] == '\\') {
        let jump = content.substr(1, 3) == '{{' ? 3 : 2;
        buffer += content.substr(1, jump)
        content = content.substr(jump)
      }
      if (content.substr(0, 2) == '{{') {
        content = content.substr(2)
        if (buffer) {
          let string = {type: 'String', content: buffer}
          obj.push(string)
          buffer = ''
        }
        get_ws()
        let expr = get_expression()
        obj.push(expr)
        if (content.substr(0, 2) != '}}') {
          throw "ParserError at get_complex_string"
        }
        content = content.substr(2)
      }
      let m = literal.exec(content)
      if (m) {
        buffer = m[1]
        content = content.substr(m[0].length)
      }
    }
    if (buffer) {
      let string = {type: 'String', content: buffer}
      obj.push(string)
    }
    content = content.substr(quote.length)
    if (obj.length == 1 && obj[0].type == 'String') {
      return obj[0]
    }
    let cs = {type: 'ComplexString', content: obj}
    return cs
  }

  function get_kvp(cl) {
    let key = get_identifier()
    get_ws()
    if (content[0] != ':') {
      throw "ParserError"
    }
    content = content.substr(1)
    get_ws()
    let val = get_value()
    let kvp = {type: cl, key: key, value: val}
    return kvp
  }

  function get_attributes() {
    if (content[0] == '>') {
      content = content.substr(1)
      return {}
    }
    let attrs = {}
    while (1) {
      let attr = get_kvp('Attribute')
      attr.local = attr.key.name[0] == '_'
      attrs[attr.key.name] = attr
      let ch = content[0]
      get_ws()
      if (content[0] == '>') {
        content = content.substr(1)
        break
      } else if (!/^\s/.test(ch)) {
        throw "ParserError"
      }
    }
    return attrs
  }

  function get_expression() {
    let exp = get_primary_expression()
    get_ws()
    return exp
  }

  function get_primary_expression() {
    if (content[0] == '$') {
      return get_variable()
    }
    return get_identifier()
  }

  function get_variable() {
    content = content.substr(1)
    let id = get_identifier()
    let ve = {type: 'VariableExpression', id: id}
    return ve
  }

  return {
    parse: parse,
  }
}
<|endoftext|>


<commit_before>import m from 'mithril';
import _ from 'underscore';
import postgrest from 'mithril-postgrest';
import h from '../h';
import models from '../models';
import rewardVM from './reward-vm';
import userVM from './user-vm';

const idVM = h.idVM,
      currentProject = m.prop(),
      userDetails = m.prop(),
      vm = postgrest.filtersVM({project_id: 'eq'});

const init = (project_id, project_user_id) => {
    vm.project_id(project_id);

    const lProject = postgrest.loaderWithToken(models.projectDetail.getRowOptions(vm.parameters()));

    fetchParallelData(project_id, project_user_id);

    return lProject.load().then((data) => currentProject(_.first(data)));
};

const fetchParallelData = (project_id, project_user_id) => {
    userVM.fetchUser(project_user_id, true, userDetails);

    rewardVM.fetchRewards(project_id);
};

const getCurrentProject = () => {
    const root = document.getElementById('project-show-root'),
          data = root && root.getAttribute('data-parameters');

    if (data) {
        const {project_id, project_user_id} = currentProject(JSON.parse(data));

        m.redraw(true);

        fetchParallelData(project_id, project_user_id);

        return currentProject();
    } else {
        return false;
    }
};

const routeToProject = (project, ref) => () => {
    console.log('Routing to project: ', project);
    currentProject(project);

    return m.route(h.buildLink(project.permalink, ref), {project_id: project.project_id, project_user_id: project.project_user_id});
};

const projectVM = {
    userDetails: userDetails,
    getCurrentProject: getCurrentProject,
    currentProject: currentProject,
    rewardDetails: rewardVM.rewards,
    routeToProject: routeToProject,
    init: init
};

export default projectVM;
<commit_msg>Updates project root element id<commit_after>import m from 'mithril';
import _ from 'underscore';
import postgrest from 'mithril-postgrest';
import h from '../h';
import models from '../models';
import rewardVM from './reward-vm';
import userVM from './user-vm';

const idVM = h.idVM,
      currentProject = m.prop(),
      userDetails = m.prop(),
      vm = postgrest.filtersVM({project_id: 'eq'});

const init = (project_id, project_user_id) => {
    vm.project_id(project_id);

    const lProject = postgrest.loaderWithToken(models.projectDetail.getRowOptions(vm.parameters()));

    fetchParallelData(project_id, project_user_id);

    return lProject.load().then((data) => currentProject(_.first(data)));
};

const fetchParallelData = (project_id, project_user_id) => {
    userVM.fetchUser(project_user_id, true, userDetails);

    rewardVM.fetchRewards(project_id);
};

const getCurrentProject = () => {
    const root = document.getElementById('application'),
          data = root && root.getAttribute('data-parameters');

    if (data) {
        const {project_id, project_user_id} = currentProject(JSON.parse(data));

        m.redraw(true);

        fetchParallelData(project_id, project_user_id);

        return currentProject();
    } else {
        return false;
    }
};

const routeToProject = (project, ref) => () => {
    console.log('Routing to project: ', project);
    currentProject(project);

    return m.route(h.buildLink(project.permalink, ref), {project_id: project.project_id, project_user_id: project.project_user_id});
};

const projectVM = {
    userDetails: userDetails,
    getCurrentProject: getCurrentProject,
    currentProject: currentProject,
    rewardDetails: rewardVM.rewards,
    routeToProject: routeToProject,
    init: init
};

export default projectVM;
<|endoftext|>


<commit_before>'use strict';

var CHAR = Object.freeze({
    EMPTY: '',
    NEWLINE: '\n',
    QUOTE: '\'',
    SEMICOLON: ';',
    SPACE: ' ',
    TAB: '    '
});

var GRAMMAR = Object.freeze({
    COLOR: 'paint',
    CREATE_START: 'create',
    CREATE_END: 'leave',
    NOTE: 'note',
    PROGRAM: 'program',
    USAGE: 'usage'
});

var indentLevel = 0;

var generateIndentation = function (level) {
    var indent = '';

    for (var i = 0; i < level; i++) {
        indent += CHAR.TAB;
    }

    return indent;
};

var generateVesselName = function (keys) {
    keys.shift();

    return keys.join('_');
};

var generatePropertyValue = function (keys) {
    keys.shift();

    return keys.join(' ');
};

module.exports = function parse (line) {
    var keys = line.match(/'[^']+'|\S+/g);
    var statement = '';

    console.log(keys)

    if (keys === null) {
        return line + '\n';
    }

    if (keys[0] === GRAMMAR.CREATE_START) {
        var vesselName = generateVesselName(keys);

        statement += generateIndentation(indentLevel) + 'function ' + vesselName  + ' () {' + CHAR.NEWLINE;

        indentLevel++;

        statement += generateIndentation(indentLevel) + 'this.name = ' + CHAR.QUOTE + vesselName + CHAR.QUOTE + CHAR.SEMICOLON + CHAR.NEWLINE;
    }

    if (keys[0] === GRAMMAR.CREATE_END) {
        indentLevel--;

        statement += generateIndentation(indentLevel) + '}' + CHAR.NEWLINE;
    }

    if (keys[0] === GRAMMAR.NOTE || keys[0] === GRAMMAR.PROGRAM || keys[0] === GRAMMAR.USAGE) {
        var propertyName = keys[0];
        var propertyValue = generatePropertyValue(keys);

        statement += generateIndentation(indentLevel) + 'this.' + propertyName + ' = ' + CHAR.QUOTE + propertyValue + CHAR.QUOTE + CHAR.SEMICOLON + CHAR.NEWLINE;
    }

    return statement;
};
<commit_msg>Remove invalid characters<commit_after>'use strict';

var CHAR = Object.freeze({
    EMPTY: '',
    NEWLINE: '\n',
    QUOTE: '\'',
    SEMICOLON: ';',
    SPACE: ' ',
    TAB: '    '
});

var GRAMMAR = Object.freeze({
    COLOR: 'paint',
    CREATE_START: 'create',
    CREATE_END: 'leave',
    NOTE: 'note',
    PROGRAM: 'program',
    USAGE: 'usage'
});

var indentLevel = 0;

var generateIndentation = function (level) {
    var indent = '';

    for (var i = 0; i < level; i++) {
        indent += CHAR.TAB;
    }

    return indent;
};

var generateVesselName = function (keys) {
    keys.shift();

    return keys.join('_');
};

var generatePropertyValue = function (keys) {
    keys.shift();

    return keys.join(' ');
};

module.exports = function parse (line) {
    line = line.replace(/[^A-Z0-9\,\. ]/gi, '');

    var keys = line.match(/'[^']+'|\S+/g);
    var statement = '';

    console.log(keys)

    if (keys === null) {
        return line + '\n';
    }

    if (keys[0] === GRAMMAR.CREATE_START) {
        var vesselName = generateVesselName(keys);

        statement += generateIndentation(indentLevel) + 'function ' + vesselName  + ' () {' + CHAR.NEWLINE;

        indentLevel++;

        statement += generateIndentation(indentLevel) + 'this.name = ' + CHAR.QUOTE + vesselName + CHAR.QUOTE + CHAR.SEMICOLON + CHAR.NEWLINE;
    }

    if (keys[0] === GRAMMAR.CREATE_END) {
        indentLevel--;

        statement += generateIndentation(indentLevel) + '}' + CHAR.NEWLINE;
    }

    if (keys[0] === GRAMMAR.NOTE || keys[0] === GRAMMAR.PROGRAM || keys[0] === GRAMMAR.USAGE) {
        var propertyName = keys[0];
        var propertyValue = generatePropertyValue(keys);

        statement += generateIndentation(indentLevel) + 'this.' + propertyName + ' = ' + CHAR.QUOTE + propertyValue + CHAR.QUOTE + CHAR.SEMICOLON + CHAR.NEWLINE;
    }

    return statement;
};
<|endoftext|>


<commit_before>function RemoteTextEditor(textBoxID, textFileSelectID, filesDir) {
	this.textBoxID = textBoxID;
	this.textFileSelectID = textFileSelectID;
	this.filesDir = filesDir;

	this.SaveFile = (function(fileName) {

	}).bind(this)

	this.GetFileList = (function() {
		// Create request

		// Create request function
	}).bind(this)

	this.LoadFileContents = (function() {

	}).bind(this)

	this.Sample = (function() {

	}).bind(this)
}<commit_msg>feat: build GetFileList method<commit_after>function RemoteTextEditor(textBoxID, textFileSelectID, filesDir) {
	this.textBoxID = textBoxID;
	this.textFileSelectID = textFileSelectID;
	this.filesDir = filesDir;
	this.xmlhttp = new XMLHttpRequest();

	this.SaveFile = (function(fileName) {

		xmlhttp.onreadystatechange = (function() {

		}).bind(this)

	}).bind(this)

	this.GetFileList = (function() {
		// Create request function
		xmlhttp.onreadystatechange = function(selectEleID){
			var filesArray = JSON.parse(xmlhttp.responseText);
			var selectEle = document.getElementById(selectEleID);
			var selectOptions = "";

			while (selectEle.length > 0) {
				selectEle.remove(0);
			}

			for (var file = 0; file < filesArray.length; file++) {
				selectEle.add(filesArray[file]);
			};

		}(this.textFileSelectID);

		// Create request
		xmlhttp.open("GET", "GetFileList.php");

		// send request
		xmlhttp.send(null);
	}).bind(this)

	this.LoadFileContents = (function() {

	}).bind(this)

	this.Sample = (function() {

	}).bind(this)
}<|endoftext|>


<commit_before>'use strict';

import Node from '@emmetio/node';
import StreamReader from '@emmetio/stream-reader';
import consumeRepeat from './repeat';
import consumeElement from './element';

const GROUP_START = 40; // (
const GROUP_END   = 41; // )
const OP_SIBLING  = 43; // +
const OP_CHILD    = 62; // >
const OP_CLIMB    = 94; // ^

/**
 * Parses given string into a node tree
 * @param  {String} str Abbreviation to parse
 * @return {Node}
 */
export default function parse(str) {
	const stream = new StreamReader(str.trim());
	const root = new Node();
	let ctx = root, groupStack = [], ch;

	while (!stream.eof()) {
		ch = stream.peek();

		if (ch === GROUP_START) { // start of group
			// The grouping node should be detached to properly handle
			// out-of-bounds `^` operator. Node will be attached right on group end
			const node = new Node();
			const groupCtx = groupStack.length ? last(groupStack)[0] : ctx;
			groupStack.push([node, groupCtx, stream.pos]);
			ctx = node;
			stream.next();
			continue;
		} else if (ch === GROUP_END) { // end of group
			const lastGroup = groupStack.pop();
			if (!lastGroup) {
				throw stream.error('Unexpected ")" group end');
			}

			const node = lastGroup[0];
			ctx = lastGroup[1];
			stream.next();

			// a group can have a repeater
			if (node.repeat = consumeRepeat(stream)) {
				ctx.appendChild(node);
			} else {
				// move all children of group into parent node
				while (node.firstChild) {
					ctx.appendChild(node.firstChild);
				}
				// for convenience, groups can be joined with optional `+` operator
				stream.eat(OP_SIBLING);
			}

			continue;
		}

		const node = consumeElement(stream);
		ctx.appendChild(node);

		if (stream.eof()) {
			break;
		}

		switch (stream.peek()) {
			case OP_SIBLING:
				stream.next();
				continue;

			case OP_CHILD:
				stream.next();
				ctx = node;
				continue;

			case OP_CLIMB:
				// it’s perfectly valid to have multiple `^` operators
				while (stream.eat(OP_CLIMB)) {
					ctx = ctx.parent || ctx;
				}
				continue;
		}
	}

	if (groupStack.length) {
		stream.pos = groupStack.pop()[2];
		throw stream.error('Expected group close');
	}

	return root;
}

function last(arr) {
	return arr[arr.length - 1];
}
<commit_msg>Support siblings after a group repeater<commit_after>'use strict';

import Node from '@emmetio/node';
import StreamReader from '@emmetio/stream-reader';
import consumeRepeat from './repeat';
import consumeElement from './element';

const GROUP_START = 40; // (
const GROUP_END   = 41; // )
const OP_SIBLING  = 43; // +
const OP_CHILD    = 62; // >
const OP_CLIMB    = 94; // ^

/**
 * Parses given string into a node tree
 * @param  {String} str Abbreviation to parse
 * @return {Node}
 */
export default function parse(str) {
	const stream = new StreamReader(str.trim());
	const root = new Node();
	let ctx = root, groupStack = [], ch;

	while (!stream.eof()) {
		ch = stream.peek();

		if (ch === GROUP_START) { // start of group
			// The grouping node should be detached to properly handle
			// out-of-bounds `^` operator. Node will be attached right on group end
			const node = new Node();
			const groupCtx = groupStack.length ? last(groupStack)[0] : ctx;
			groupStack.push([node, groupCtx, stream.pos]);
			ctx = node;
			stream.next();
			continue;
		} else if (ch === GROUP_END) { // end of group
			const lastGroup = groupStack.pop();
			if (!lastGroup) {
				throw stream.error('Unexpected ")" group end');
			}

			const node = lastGroup[0];
			ctx = lastGroup[1];
			stream.next();

			// a group can have a repeater
			if (node.repeat = consumeRepeat(stream)) {
				ctx.appendChild(node);
			} else {
				// move all children of group into parent node
				while (node.firstChild) {
					ctx.appendChild(node.firstChild);
				}
			}
			// for convenience, groups can be joined with optional `+` operator
			stream.eat(OP_SIBLING);

			continue;
		}

		const node = consumeElement(stream);
		ctx.appendChild(node);

		if (stream.eof()) {
			break;
		}

		switch (stream.peek()) {
			case OP_SIBLING:
				stream.next();
				continue;

			case OP_CHILD:
				stream.next();
				ctx = node;
				continue;

			case OP_CLIMB:
				// it’s perfectly valid to have multiple `^` operators
				while (stream.eat(OP_CLIMB)) {
					ctx = ctx.parent || ctx;
				}
				continue;
		}
	}

	if (groupStack.length) {
		stream.pos = groupStack.pop()[2];
		throw stream.error('Expected group close');
	}

	return root;
}

function last(arr) {
	return arr[arr.length - 1];
}
<|endoftext|>


<commit_before>var fs = require('fs');
var lodash = require('lodash');

var parser = module.exports = function(path, callback) {
  fs.readFile(path, 'utf-8', function(err, file) {
    file = file.split('\n');
    var course = {};
    for(var i = 0; i < file.length; i++) {
      var char = 0;
      var prefix = [];
      while(file[i][char] === '#') {
        prefix.push(file[i][char]);
        char++;
      }
    }
    return callback(err, course);
  });
};
<commit_msg>added while loop to parser<commit_after>var fs = require('fs');
var lodash = require('lodash');

var parser = module.exports = function(path, callback) {
  fs.readFile(path, 'utf-8', function(err, file) {
    file = file.split('\n');
    var course = {};

    for(var i = 0; i < file.length; i++) {

      var char = 0;
      var prefix = [];
      while(file[i][char] === '#') {
        prefix.push(file[i][char]);
        char++;
      }

    }
    return callback(err, course);
  });
};
<|endoftext|>


<commit_before>import path from 'path';
import glob from 'glob';
import fs from 'fs';
import async from 'async';

import {

	SourceMapGenerator,
	SourceMapConsumer

} from 'source-map';

import { MonicError } from './error';
import { FileStructure } from './file';
import { $C } from 'collection.js';

/**
 * Parser class
 */
export default class Parser {
	/**
	 * @param {{
	 *   lineSeparator: string,
	 *   replacers: !Array,
	 *   sourceMap: (SourceMapConsumer|undefined)
	 * }} params - additional parameters:
	 *   *) params.lineSeparator - EOL symbol;
	 *   *) params.replacers - an array of transform functions;
	 *   *) params.sourceMap - a source map object.
	 */
	constructor(params) {
		this.nl = params.lineSeparator;
		this.sourceMap = params.sourceMap || new SourceMapGenerator();
		this.replacers = params.replacers || [];
		this.realpathCache = {};
		this.cache = {};
	}

	/**
	 * Checks a file for existence
	 * and returns an absolute path to it
	 *
	 * @param {string} file - the file path
	 * @param {function(Error, string=)} callback - a callback function
	 */
	normalizePath(file, callback) {
		file = path.normalize(path.resolve(file));

		if (this.realpathCache[file]) {
			callback(null, file);

		} else {
			async.waterfall([
				(callback) => {
					fs.exists(file, (exists) => {
						callback(null, exists);
					});
				},

				(exists, callback) => {
					if (!exists) {
						return callback(new Error(`File "${file}" not found`));
					}

					fs.stat(file, (err, stat) => {
						callback(err, stat);
					});
				},

				(stat, callback) => {
					if (!stat.isFile()) {
						return callback(new Error(`"${file}" is not a file`));
					}

					this.realpathCache[file] = true;
					callback(null, file);
				}

			], callback);
		}
	}

	/**
	 * Parses a file and returns it structure
	 *
	 * @param {string} file - the file path
	 * @param {function(Error, FileStructure=, string=)} callback - a callback function
	 */
	parseFile(file, callback) {
		this.normalizePath(file, (err, src) => {
			if (err) {
				return callback(err);
			}

			if (this.cache[src]) {
				return callback(null, this.cache[src], src);
			}

			fs.readFile(src, 'utf8', (err, content) => {
				if (err) {
					return callback(err);
				}

				this.parse(src, content, callback);
			});
		});
	}

	/**
	 * Parses a text and returns it structure
	 *
	 * @param {string} file - a path to a file
	 * @param {string} content - the source text
	 * @param {function(Error, FileStructure=, string=)} callback - a callback function
	 */
	parse(file, content, callback) {
		if (this.cache[file]) {
			return callback(null, this.cache[file], file);
		}

		const
			actions = [],
			dirname = path.dirname(file);

		content = this.replacers.reduce((content, el) => {
			content = el(content, file);
			return content;

		}, content);

		// URL transformers
		content = content.replace(/^(\s*\/\/(?:#include|without)\s+)(.*)/gm, (sstr, decl, src) => {
			if (/\*/.test(src)) {
				actions.push((callback) => {
					const parts = src.split('::');
					glob(path.join(dirname, parts[0]), null, (err, files) => {
						if (files) {
							content = content.replace(sstr, files.reduce((res, el) => {
								parts[0] = path.relative(dirname, el);
								res += decl + parts.join('::') + this.nl;
								return res;
							}, ''));
						}

						callback(err, files);
					});
				});
			}

			return sstr;
		});

		async.parallel(actions, (err) => {
			if (err) {
				return callback(err);
			}

			const
				fileStructure = new FileStructure(file, this.nl),
				lines = content.split(/\r?\n|\r/);

			this.cache[file] = fileStructure;
			const parseLines = (start) => {
				const
					errors = [];

				let i;

				function appendError(err) {
					const
						msg = err.message,
						line = i + 1;

					errors.push(new MonicError(msg, file, line));
					fileStructure.error(msg);
				}

				function asyncParseCallback(err) {
					if (err) {
						appendError(err);
					}

					parseLines(i + 1);
				}

				for (i = start; i < lines.length; i++) {
					const line = lines[i];
					const info = {
						generated: {
							line: i,
							column: 0
						},

						original: {
							line: i,
							column: 0
						},

						source: file
					};

					if (line.match(/^\s*\/\/#(.*)/)) {
						if (RegExp.$1) {
							const
								command = RegExp.$1.split(' '),
								dir = String(command.shift());

							const
								key = '_' + dir,
								params = command.join(' ');

							if (/^(include|without)$/.test(dir)) {
								return this[key](fileStructure, params, info, asyncParseCallback);

							} else if (/^(label|endlabel|if|endif|set|unset)$/.test(dir)) {
								try {
									this[key](fileStructure, params, info);

								} catch (err) {
									appendError(err);
								}

							} else {
								appendError(new Error(`Unknown directive ${dir}`));
							}
						}

					} else {
						fileStructure.addCode(line + (i < lines.length - 1 ? this.nl : ''), info);
					}
				}

				callback(null, fileStructure, file);
			};

			parseLines(0);
		});
	}

	/**
	 * Directive #include
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {string} value - a value of the directive
	 * @param {!Object} info - an information object
	 * @param {function(Error=)} callback - a callback function
	 */
	_include(file, value, info, callback) {
		let
			paramsParts = value.split('::'),
			includeFileName = paramsParts.shift();

		paramsParts = paramsParts.reduce((res, el) => {
			res[el] = true;
			return res;
		}, {});

		if (includeFileName) {
			this.parseFile(file.getRelativePathOf(includeFileName), (err, includeFile) => {
				if (err) {
					return callback(err);
				}

				file.addInclude(includeFile, paramsParts);
				callback();
			});

		} else {
			$C(paramsParts).forEach((el, key) => {
				file.root.labels[key] = true;
			});

			callback();
		}
	}

	/**
	 * Directive #without
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {string} value - a value of the directive
	 * @param {!Object} info - an information object
	 * @param {function(Error=)} callback - a callback function
	 */
	_without(file, value, info, callback) {
		let
			paramsParts = value.split('::'),
			includeFname = file.getRelativePathOf(paramsParts.shift());

		paramsParts = paramsParts.reduce((res, el) => {
			res[el] = true;
			return res;
		}, {});

		this.parseFile(includeFname, (err, includeFile) => {
			if (err) {
				return callback(err);
			}

			file.addWithout(includeFile, paramsParts);
			callback();
		});
	}

	/**
	 * Directive #label
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {string} value - a value of the directive
	 * @param {!Object} info - an information object
	 */
	_label(file, value, info) {
		file.beginLabel(value);
	}

	/**
	 * Directive #endlabel
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 */
	_endlabel(file, info) {
		file.endLabel();
	}

	/**
	 * Directive #if
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 * @param {string} value - a value of the directive
	 */
	_if(file, info, value) {
		if (!value.trim()) {
			throw new Error('Bad "if" directive');
		}

		const
			args = value.split(/\s+/);

		let res = true;
		if (args.length > 1 && args[0] === 'not') {
			res = false;
			args.shift();
		}

		file.beginIf(args[0], res);
	}

	/**
	 * Directive #endif
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 */
	_endif(file, info) {
		file.endIf(info);
	}

	/**
	 * Directive #set
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 * @param {string} value - a value of the directive
	 */
	_set(file, info, value) {
		if (!value.trim()) {
			throw new Error('Bad "set" directive');
		}

		file.addSet(value.split(/\s+/)[0], info);
	}

	/**
	 * Directive #unset
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 * @param {string} value - a value of the directive
	 */
	_unset(file, info, value) {
		if (!value.trim()) {
			throw new Error('Bad "unset" directive');
		}

		file.addUnset(value.split(/\s+/)[0], info);
	}
}
<commit_msg>Working on support for sourcemaps<commit_after>import path from 'path';
import glob from 'glob';
import fs from 'fs';
import async from 'async';

import {

	SourceMapGenerator,
	SourceMapConsumer

} from 'source-map';

import { MonicError } from './error';
import { FileStructure } from './file';
import { $C } from 'collection.js';

/**
 * Parser class
 */
export default class Parser {
	/**
	 * @param {{
	 *   lineSeparator: string,
	 *   replacers: !Array,
	 *   sourceMap: (SourceMapConsumer|undefined)
	 * }} params - additional parameters:
	 *   *) params.lineSeparator - EOL symbol;
	 *   *) params.replacers - an array of transform functions;
	 *   *) params.sourceMap - a source map object.
	 */
	constructor(params) {
		this.nl = params.lineSeparator;
		this.sourceMap = params.sourceMap || new SourceMapGenerator();
		this.replacers = params.replacers || [];
		this.realpathCache = {};
		this.cache = {};
	}

	/**
	 * Checks a file for existence
	 * and returns an absolute path to it
	 *
	 * @param {string} file - the file path
	 * @param {function(Error, string=)} callback - a callback function
	 */
	normalizePath(file, callback) {
		file = path.normalize(path.resolve(file));

		if (this.realpathCache[file]) {
			callback(null, file);

		} else {
			async.waterfall([
				(callback) => {
					fs.exists(file, (exists) => {
						callback(null, exists);
					});
				},

				(exists, callback) => {
					if (!exists) {
						return callback(new Error(`File "${file}" not found`));
					}

					fs.stat(file, (err, stat) => {
						callback(err, stat);
					});
				},

				(stat, callback) => {
					if (!stat.isFile()) {
						return callback(new Error(`"${file}" is not a file`));
					}

					this.realpathCache[file] = true;
					callback(null, file);
				}

			], callback);
		}
	}

	/**
	 * Parses a file and returns it structure
	 *
	 * @param {string} file - the file path
	 * @param {function(Error, FileStructure=, string=)} callback - a callback function
	 */
	parseFile(file, callback) {
		this.normalizePath(file, (err, src) => {
			if (err) {
				return callback(err);
			}

			if (this.cache[src]) {
				return callback(null, this.cache[src], src);
			}

			fs.readFile(src, (err, content) => {
				if (err) {
					return callback(err);
				}

				this.parse(src, content, callback);
			});
		});
	}

	/**
	 * Parses a text and returns it structure
	 *
	 * @param {string} file - a path to a file
	 * @param {string} content - the source text
	 * @param {function(Error, FileStructure=, string=)} callback - a callback function
	 */
	parse(file, content, callback) {
		if (this.cache[file]) {
			return callback(null, this.cache[file], file);
		}

		const
			actions = [],
			dirname = path.dirname(file);

		content = this.replacers.reduce((content, el) => {
			content = el(content, file);
			return content;

		}, content);

		// URL transformers
		content = content.replace(/^(\s*\/\/(?:#include|without)\s+)(.*)/gm, (sstr, decl, src) => {
			if (/\*/.test(src)) {
				actions.push((callback) => {
					const parts = src.split('::');
					glob(path.join(dirname, parts[0]), null, (err, files) => {
						if (files) {
							content = content.replace(sstr, files.reduce((res, el) => {
								parts[0] = path.relative(dirname, el);
								res += decl + parts.join('::') + this.nl;
								return res;
							}, ''));
						}

						callback(err, files);
					});
				});
			}

			return sstr;
		});

		async.parallel(actions, (err) => {
			if (err) {
				return callback(err);
			}

			const
				fileStructure = new FileStructure(file, this.nl),
				lines = content.split(/\r?\n|\r/);

			this.cache[file] = fileStructure;
			const parseLines = (start) => {
				const
					errors = [];

				let
					info,
					i;

				function appendError(err) {
					const
						msg = err.message,
						line = i + 1;

					errors.push(new MonicError(msg, file, line));
					fileStructure.error(msg, info);
				}

				function asyncParseCallback(err) {
					if (err) {
						appendError(err);
					}

					parseLines(i + 1);
				}

				for (i = start; i < lines.length; i++) {
					const line = lines[i];
					info = {
						generated: {
							line: i,
							column: 0
						},

						original: {
							line: i,
							column: 0
						},

						source: file
					};

					if (line.match(/^\s*\/\/#(.*)/)) {
						if (RegExp.$1) {
							const
								command = RegExp.$1.split(' '),
								dir = String(command.shift());

							const
								key = '_' + dir,
								params = command.join(' ');

							if (/^(include|without)$/.test(dir)) {
								return this[key](fileStructure, info, params, asyncParseCallback);

							} else if (/^(label|endlabel|if|endif|set|unset)$/.test(dir)) {
								try {
									this[key](fileStructure, info, params);

								} catch (err) {
									appendError(err);
								}

							} else {
								appendError(new Error(`Unknown directive ${dir}`));
							}
						}

					} else {
						fileStructure.addCode(line + (i < lines.length - 1 ? this.nl : ''), info);
					}
				}

				callback(null, fileStructure, file);
			};

			parseLines(0);
		});
	}

	/**
	 * Directive #include
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {string} value - a value of the directive
	 * @param {!Object} info - an information object
	 * @param {function(Error=)} callback - a callback function
	 */
	_include(file, value, info, callback) {
		let
			paramsParts = value.split('::'),
			includeFileName = paramsParts.shift();

		paramsParts = paramsParts.reduce((res, el) => {
			res[el] = true;
			return res;
		}, {});

		if (includeFileName) {
			this.parseFile(file.getRelativePathOf(includeFileName), (err, includeFile) => {
				if (err) {
					return callback(err);
				}

				file.addInclude(includeFile, paramsParts, info);
				callback();
			});

		} else {
			$C(paramsParts).forEach((el, key) => {
				file.root.labels[key] = true;
			});

			callback();
		}
	}

	/**
	 * Directive #without
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {string} value - a value of the directive
	 * @param {!Object} info - an information object
	 * @param {function(Error=)} callback - a callback function
	 */
	_without(file, value, info, callback) {
		let
			paramsParts = value.split('::'),
			includeFname = file.getRelativePathOf(paramsParts.shift());

		paramsParts = paramsParts.reduce((res, el) => {
			res[el] = true;
			return res;
		}, {});

		this.parseFile(includeFname, (err, includeFile) => {
			if (err) {
				return callback(err);
			}

			file.addWithout(includeFile, paramsParts, info);
			callback();
		});
	}

	/**
	 * Directive #label
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {string} value - a value of the directive
	 * @param {!Object} info - an information object
	 */
	_label(file, value, info) {
		file.beginLabel(value, info);
	}

	/**
	 * Directive #endlabel
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 */
	_endlabel(file, info) {
		file.endLabel(info);
	}

	/**
	 * Directive #if
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 * @param {string} value - a value of the directive
	 */
	_if(file, info, value) {
		if (!value.trim()) {
			throw new Error('Bad "if" directive');
		}

		const
			args = value.split(/\s+/);

		let res = true;
		if (args.length > 1 && args[0] === 'not') {
			res = false;
			args.shift();
		}

		file.beginIf(args[0], res, info);
	}

	/**
	 * Directive #endif
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 */
	_endif(file, info) {
		file.endIf(info);
	}

	/**
	 * Directive #set
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 * @param {string} value - a value of the directive
	 */
	_set(file, info, value) {
		if (!value.trim()) {
			throw new Error('Bad "set" directive');
		}

		file.addSet(value.split(/\s+/)[0], info);
	}

	/**
	 * Directive #unset
	 *
	 * @private
	 * @param {!FileStructure} file - a file structure
	 * @param {!Object} info - an information object
	 * @param {string} value - a value of the directive
	 */
	_unset(file, info, value) {
		if (!value.trim()) {
			throw new Error('Bad "unset" directive');
		}

		file.addUnset(value.split(/\s+/)[0], info);
	}
}
<|endoftext|>


<commit_before>"use strict";

var assert = require("assert");
var types = require("./types");
var n = types.namedTypes;
var b = types.builders;
var isObject = types.builtInTypes.object;
var isArray = types.builtInTypes.array;
var isFunction = types.builtInTypes.function;
var Patcher = require("./patcher").Patcher;
var normalizeOptions = require("./options").normalize;
var fromString = require("./lines").fromString;
var attachComments = require("./comments").attach;
var util = require("./util");

exports.parse = function parse(source, options) {
  options = normalizeOptions(options);

  const lines = fromString(source, options);

  const sourceWithoutTabs = lines.toString({
    tabWidth: options.tabWidth,
    reuseWhitespace: false,
    useTabs: false
  });

  let comments = [];
  const ast = options.parser.parse(sourceWithoutTabs, {
    jsx: true,
    loc: true,
    locations: true,
    range: options.range,
    comment: true,
    onComment: comments,
    tolerant: util.getOption(options, "tolerant", true),
    ecmaVersion: 6,
    sourceType: util.getOption(options, "sourceType", "module")
  });

  // Use ast.tokens if possible, and otherwise fall back to the Esprima
  // tokenizer. All the preconfigured ../parsers/* expose ast.tokens
  // automatically, but custom parsers might need additional configuration
  // to avoid this fallback.
  const tokens = Array.isArray(ast.tokens)
    ? ast.tokens
    : require("esprima").tokenize(sourceWithoutTabs, {
        loc: true
      });

  // We will reattach the tokens array to the file object below.
  delete ast.tokens;

  if (Array.isArray(ast.comments)) {
    comments = ast.comments;
    delete ast.comments;
  }

  if (ast.loc) {
    // If the source was empty, some parsers give loc.{start,end}.line
    // values of 0, instead of the minimum of 1.
    util.fixFaultyLocations(ast, lines);
  } else {
    ast.loc = {
      start: lines.firstPos(),
      end: lines.lastPos()
    };
  }

  ast.loc.lines = lines;
  ast.loc.indent = 0;

  let file;
  let program;
  if (ast.type === "Program") {
    program = ast;
    // In order to ensure we reprint leading and trailing program
    // comments, wrap the original Program node with a File node. Only
    // ESTree parsers (Acorn and Esprima) return a Program as the root AST
    // node. Most other (Babylon-like) parsers return a File.
    file = b.file(ast, options.sourceFileName || null);
    file.loc = {
      start: lines.firstPos(),
      end: lines.lastPos(),
      lines: lines,
      indent: 0
    };
  } else if (ast.type === "File") {
    file = ast;
    program = file.program;
  }

  // Expose file.tokens unless the caller passed false for options.tokens.
  if (options.tokens) {
    file.tokens = tokens;
  }

  // Expand the Program's .loc to include all comments (not just those
  // attached to the Program node, as its children may have comments as
  // well), since sometimes program.loc.{start,end} will coincide with the
  // .loc.{start,end} of the first and last *statements*, mistakenly
  // excluding comments that fall outside that region.
  var trueProgramLoc = util.getTrueLoc({
    type: program.type,
    loc: program.loc,
    body: [],
    comments
  }, lines);
  program.loc.start = trueProgramLoc.start;
  program.loc.end = trueProgramLoc.end;

  // Passing file.program here instead of just file means that initial
  // comments will be attached to program.body[0] instead of program.
  attachComments(
    comments,
    program.body.length ? file.program : file,
    lines
  );

  // Return a copy of the original AST so that any changes made may be
  // compared to the original.
  return new TreeCopier(lines, tokens).copy(file);
};

function TreeCopier(lines, tokens) {
  assert.ok(this instanceof TreeCopier);
  this.lines = lines;
  this.tokens = tokens;
  this.startTokenIndex = 0;
  this.endTokenIndex = tokens.length;
  this.indent = 0;
  this.seen = new Map;
}

var TCp = TreeCopier.prototype;

TCp.copy = function(node) {
  if (this.seen.has(node)) {
    return this.seen.get(node);
  }

  if (isArray.check(node)) {
    var copy = new Array(node.length);
    this.seen.set(node, copy);
    node.forEach(function (item, i) {
      copy[i] = this.copy(item);
    }, this);
    return copy;
  }

  if (!isObject.check(node)) {
    return node;
  }

  util.fixFaultyLocations(node, this.lines);

  var copy = Object.create(Object.getPrototypeOf(node), {
    original: { // Provide a link from the copy to the original.
      value: node,
      configurable: false,
      enumerable: false,
      writable: true
    }
  });

  this.seen.set(node, copy);

  var loc = node.loc;
  var oldIndent = this.indent;
  var newIndent = oldIndent;

  const oldStartTokenIndex = this.startTokenIndex;
  const oldEndTokenIndex = this.endTokenIndex;

  if (loc) {
    // When node is a comment, we set node.loc.indent to
    // node.loc.start.column so that, when/if we print the comment by
    // itself, we can strip that much whitespace from the left margin of
    // the comment. This only really matters for multiline Block comments,
    // but it doesn't hurt for Line comments.
    if (node.type === "Block" || node.type === "Line" ||
        node.type === "CommentBlock" || node.type === "CommentLine" ||
        this.lines.isPrecededOnlyByWhitespace(loc.start)) {
      newIndent = this.indent = loc.start.column;
    }

    // Every node.loc has a reference to the original source lines as well
    // as a complete list of source tokens.
    loc.lines = this.lines;
    loc.tokens = this.tokens;
    loc.indent = newIndent;

    // Set loc.start.token and loc.end.token such that
    // loc.tokens.slice(loc.start.token, loc.end.token) returns a list of
    // all the tokens that make up this node.
    this.findTokenRange(loc);
  }

  var keys = Object.keys(node);
  var keyCount = keys.length;
  for (var i = 0; i < keyCount; ++i) {
    var key = keys[i];
    if (key === "loc") {
      copy[key] = node[key];
    } else if (key === "tokens" &&
               node.type === "File") {
      // Preserve file.tokens (uncopied) in case client code cares about
      // it, even though Recast ignores it when reprinting.
      copy[key] = node[key];
    } else {
      copy[key] = this.copy(node[key]);
    }
  }

  this.indent = oldIndent;
  this.startTokenIndex = oldStartTokenIndex;
  this.endTokenIndex = oldEndTokenIndex;

  return copy;
};

// If we didn't have any idea where in loc.tokens to look for tokens
// contained by this loc, a binary search would be appropriate, but
// because we maintain this.startTokenIndex and this.endTokenIndex as we
// traverse the AST, we only need to make small (linear) adjustments to
// those indexes with each recursive iteration.
TCp.findTokenRange = function (loc) {
  // In the unlikely event that loc.tokens[this.startTokenIndex] starts
  // *after* loc.start, we need to rewind this.startTokenIndex first.
  while (this.startTokenIndex > 0) {
    const token = loc.tokens[this.startTokenIndex];
    if (util.comparePos(loc.start, token.loc.start) < 0) {
      --this.startTokenIndex;
    } else break;
  }

  // In the unlikely event that loc.tokens[this.endTokenIndex - 1] ends
  // *before* loc.end, we need to fast-forward this.endTokenIndex first.
  while (this.endTokenIndex < loc.tokens.length) {
    const token = loc.tokens[this.endTokenIndex];
    if (util.comparePos(token.loc.end, loc.end) < 0) {
      ++this.endTokenIndex;
    } else break;
  }

  // Increment this.startTokenIndex until we've found the first token
  // contained by this node.
  while (this.startTokenIndex < this.endTokenIndex) {
    const token = loc.tokens[this.startTokenIndex];
    if (util.comparePos(token.loc.start, loc.start) < 0) {
      ++this.startTokenIndex;
    } else break;
  }

  // Index into loc.tokens of the first token within this node.
  loc.start.token = this.startTokenIndex;

  // Decrement this.endTokenIndex until we've found the first token after
  // this node (not contained by the node).
  while (this.endTokenIndex > this.startTokenIndex) {
    const token = loc.tokens[this.endTokenIndex - 1];
    if (util.comparePos(loc.end, token.loc.end) < 0) {
      --this.endTokenIndex;
    } else break;
  }

  // Index into loc.tokens of the first token *after* this node.
  // If loc.start.token === loc.end.token, the node contains no tokens,
  // and the index is that of the next token following this node.
  loc.end.token = this.endTokenIndex;
};
<commit_msg>Make sure every token has a token.value string.<commit_after>"use strict";

var assert = require("assert");
var types = require("./types");
var n = types.namedTypes;
var b = types.builders;
var isObject = types.builtInTypes.object;
var isArray = types.builtInTypes.array;
var isFunction = types.builtInTypes.function;
var Patcher = require("./patcher").Patcher;
var normalizeOptions = require("./options").normalize;
var fromString = require("./lines").fromString;
var attachComments = require("./comments").attach;
var util = require("./util");

exports.parse = function parse(source, options) {
  options = normalizeOptions(options);

  const lines = fromString(source, options);

  const sourceWithoutTabs = lines.toString({
    tabWidth: options.tabWidth,
    reuseWhitespace: false,
    useTabs: false
  });

  let comments = [];
  const ast = options.parser.parse(sourceWithoutTabs, {
    jsx: true,
    loc: true,
    locations: true,
    range: options.range,
    comment: true,
    onComment: comments,
    tolerant: util.getOption(options, "tolerant", true),
    ecmaVersion: 6,
    sourceType: util.getOption(options, "sourceType", "module")
  });

  // Use ast.tokens if possible, and otherwise fall back to the Esprima
  // tokenizer. All the preconfigured ../parsers/* expose ast.tokens
  // automatically, but custom parsers might need additional configuration
  // to avoid this fallback.
  const tokens = Array.isArray(ast.tokens)
    ? ast.tokens
    : require("esprima").tokenize(sourceWithoutTabs, {
        loc: true
      });

  // We will reattach the tokens array to the file object below.
  delete ast.tokens;

  // Make sure every token has a token.value string.
  tokens.forEach(function (token) {
    if (typeof token.value !== "string") {
      token.value = lines.sliceString(token.loc.start, token.loc.end);
    }
  });

  if (Array.isArray(ast.comments)) {
    comments = ast.comments;
    delete ast.comments;
  }

  if (ast.loc) {
    // If the source was empty, some parsers give loc.{start,end}.line
    // values of 0, instead of the minimum of 1.
    util.fixFaultyLocations(ast, lines);
  } else {
    ast.loc = {
      start: lines.firstPos(),
      end: lines.lastPos()
    };
  }

  ast.loc.lines = lines;
  ast.loc.indent = 0;

  let file;
  let program;
  if (ast.type === "Program") {
    program = ast;
    // In order to ensure we reprint leading and trailing program
    // comments, wrap the original Program node with a File node. Only
    // ESTree parsers (Acorn and Esprima) return a Program as the root AST
    // node. Most other (Babylon-like) parsers return a File.
    file = b.file(ast, options.sourceFileName || null);
    file.loc = {
      start: lines.firstPos(),
      end: lines.lastPos(),
      lines: lines,
      indent: 0
    };
  } else if (ast.type === "File") {
    file = ast;
    program = file.program;
  }

  // Expose file.tokens unless the caller passed false for options.tokens.
  if (options.tokens) {
    file.tokens = tokens;
  }

  // Expand the Program's .loc to include all comments (not just those
  // attached to the Program node, as its children may have comments as
  // well), since sometimes program.loc.{start,end} will coincide with the
  // .loc.{start,end} of the first and last *statements*, mistakenly
  // excluding comments that fall outside that region.
  var trueProgramLoc = util.getTrueLoc({
    type: program.type,
    loc: program.loc,
    body: [],
    comments
  }, lines);
  program.loc.start = trueProgramLoc.start;
  program.loc.end = trueProgramLoc.end;

  // Passing file.program here instead of just file means that initial
  // comments will be attached to program.body[0] instead of program.
  attachComments(
    comments,
    program.body.length ? file.program : file,
    lines
  );

  // Return a copy of the original AST so that any changes made may be
  // compared to the original.
  return new TreeCopier(lines, tokens).copy(file);
};

function TreeCopier(lines, tokens) {
  assert.ok(this instanceof TreeCopier);
  this.lines = lines;
  this.tokens = tokens;
  this.startTokenIndex = 0;
  this.endTokenIndex = tokens.length;
  this.indent = 0;
  this.seen = new Map;
}

var TCp = TreeCopier.prototype;

TCp.copy = function(node) {
  if (this.seen.has(node)) {
    return this.seen.get(node);
  }

  if (isArray.check(node)) {
    var copy = new Array(node.length);
    this.seen.set(node, copy);
    node.forEach(function (item, i) {
      copy[i] = this.copy(item);
    }, this);
    return copy;
  }

  if (!isObject.check(node)) {
    return node;
  }

  util.fixFaultyLocations(node, this.lines);

  var copy = Object.create(Object.getPrototypeOf(node), {
    original: { // Provide a link from the copy to the original.
      value: node,
      configurable: false,
      enumerable: false,
      writable: true
    }
  });

  this.seen.set(node, copy);

  var loc = node.loc;
  var oldIndent = this.indent;
  var newIndent = oldIndent;

  const oldStartTokenIndex = this.startTokenIndex;
  const oldEndTokenIndex = this.endTokenIndex;

  if (loc) {
    // When node is a comment, we set node.loc.indent to
    // node.loc.start.column so that, when/if we print the comment by
    // itself, we can strip that much whitespace from the left margin of
    // the comment. This only really matters for multiline Block comments,
    // but it doesn't hurt for Line comments.
    if (node.type === "Block" || node.type === "Line" ||
        node.type === "CommentBlock" || node.type === "CommentLine" ||
        this.lines.isPrecededOnlyByWhitespace(loc.start)) {
      newIndent = this.indent = loc.start.column;
    }

    // Every node.loc has a reference to the original source lines as well
    // as a complete list of source tokens.
    loc.lines = this.lines;
    loc.tokens = this.tokens;
    loc.indent = newIndent;

    // Set loc.start.token and loc.end.token such that
    // loc.tokens.slice(loc.start.token, loc.end.token) returns a list of
    // all the tokens that make up this node.
    this.findTokenRange(loc);
  }

  var keys = Object.keys(node);
  var keyCount = keys.length;
  for (var i = 0; i < keyCount; ++i) {
    var key = keys[i];
    if (key === "loc") {
      copy[key] = node[key];
    } else if (key === "tokens" &&
               node.type === "File") {
      // Preserve file.tokens (uncopied) in case client code cares about
      // it, even though Recast ignores it when reprinting.
      copy[key] = node[key];
    } else {
      copy[key] = this.copy(node[key]);
    }
  }

  this.indent = oldIndent;
  this.startTokenIndex = oldStartTokenIndex;
  this.endTokenIndex = oldEndTokenIndex;

  return copy;
};

// If we didn't have any idea where in loc.tokens to look for tokens
// contained by this loc, a binary search would be appropriate, but
// because we maintain this.startTokenIndex and this.endTokenIndex as we
// traverse the AST, we only need to make small (linear) adjustments to
// those indexes with each recursive iteration.
TCp.findTokenRange = function (loc) {
  // In the unlikely event that loc.tokens[this.startTokenIndex] starts
  // *after* loc.start, we need to rewind this.startTokenIndex first.
  while (this.startTokenIndex > 0) {
    const token = loc.tokens[this.startTokenIndex];
    if (util.comparePos(loc.start, token.loc.start) < 0) {
      --this.startTokenIndex;
    } else break;
  }

  // In the unlikely event that loc.tokens[this.endTokenIndex - 1] ends
  // *before* loc.end, we need to fast-forward this.endTokenIndex first.
  while (this.endTokenIndex < loc.tokens.length) {
    const token = loc.tokens[this.endTokenIndex];
    if (util.comparePos(token.loc.end, loc.end) < 0) {
      ++this.endTokenIndex;
    } else break;
  }

  // Increment this.startTokenIndex until we've found the first token
  // contained by this node.
  while (this.startTokenIndex < this.endTokenIndex) {
    const token = loc.tokens[this.startTokenIndex];
    if (util.comparePos(token.loc.start, loc.start) < 0) {
      ++this.startTokenIndex;
    } else break;
  }

  // Index into loc.tokens of the first token within this node.
  loc.start.token = this.startTokenIndex;

  // Decrement this.endTokenIndex until we've found the first token after
  // this node (not contained by the node).
  while (this.endTokenIndex > this.startTokenIndex) {
    const token = loc.tokens[this.endTokenIndex - 1];
    if (util.comparePos(loc.end, token.loc.end) < 0) {
      --this.endTokenIndex;
    } else break;
  }

  // Index into loc.tokens of the first token *after* this node.
  // If loc.start.token === loc.end.token, the node contains no tokens,
  // and the index is that of the next token following this node.
  loc.end.token = this.endTokenIndex;
};
<|endoftext|>