function parseOptionsExpression(optionsExp, selectElement, scope) {

    var match = optionsExp.match(NG_OPTIONS_REGEXP);
    if (!(match)) {
      throw ngOptionsMinErr('iexp',
        "Expected expression in form of " +
        "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" +
        " but got '{0}'. Element: {1}",
        optionsExp, startingTag(selectElement));
    }

    // Extract the parts from the ngOptions expression

    // The variable name for the value of the item in the collection
    var valueName = match[5] || match[7];
    // The variable name for the key of the item in the collection
    var keyName = match[6];

    // An expression that generates the viewValue for an option if there is a label expression
    var selectAs = / as /.test(match[0]) && match[1];
    // An expression that is used to track the id of each object in the options collection
    var trackBy = match[9];
    // An expression that generates the viewValue for an option if there is no label expression
    var valueFn = $parse(match[2] ? match[1] : valueName);
    var selectAsFn = selectAs && $parse(selectAs);
    var viewValueFn = selectAsFn || valueFn;
    var trackByFn = trackBy && $parse(trackBy);

    // Get the value by which we are going to track the option
    // if we have a trackFn then use that (passing scope and locals)
    // otherwise just hash the given viewValue
    var getTrackByValueFn = trackBy ?
                              function(value, locals) { return trackByFn(scope, locals); } :
                              function getHashOfValue(value) { return hashKey(value); };
    var getTrackByValue = function(value, key) {
      return getTrackByValueFn(value, getLocals(value, key));
    };

    var displayFn = $parse(match[2] || match[1]);
    var groupByFn = $parse(match[3] || '');
    var disableWhenFn = $parse(match[4] || '');
    var valuesFn = $parse(match[8]);

    var locals = {};
    var getLocals = keyName ? function(value, key) {
      locals[keyName] = key;
      locals[valueName] = value;
      return locals;
    } : function(value) {
      locals[valueName] = value;
      return locals;
    };


    function Option(selectValue, viewValue, label, group, disabled) {
      this.selectValue = selectValue;
      this.viewValue = viewValue;
      this.label = label;
      this.group = group;
      this.disabled = disabled;
    }

    function getOptionValuesKeys(optionValues) {
      var optionValuesKeys;

      if (!keyName && isArrayLike(optionValues)) {
        optionValuesKeys = optionValues;
      } else {
        // if object, extract keys, in enumeration order, unsorted
        optionValuesKeys = [];
        for (var itemKey in optionValues) {
          if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') {
            optionValuesKeys.push(itemKey);
          }
        }
      }
      return optionValuesKeys;
    }

    return {
      trackBy: trackBy,
      getTrackByValue: getTrackByValue,
      getWatchables: $parse(valuesFn, function(optionValues) {
        // Create a collection of things that we would like to watch (watchedArray)
        // so that they can all be watched using a single $watchCollection
        // that only runs the handler once if anything changes
        var watchedArray = [];
        optionValues = optionValues || [];

        var optionValuesKeys = getOptionValuesKeys(optionValues);
        var optionValuesLength = optionValuesKeys.length;
        for (var index = 0; index < optionValuesLength; index++) {
          var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
          var value = optionValues[key];

          var locals = getLocals(optionValues[key], key);
          var selectValue = getTrackByValueFn(optionValues[key], locals);
          watchedArray.push(selectValue);

          // Only need to watch the displayFn if there is a specific label expression
          if (match[2] || match[1]) {
            var label = displayFn(scope, locals);
            watchedArray.push(label);
          }

          // Only need to watch the disableWhenFn if there is a specific disable expression
          if (match[4]) {
            var disableWhen = disableWhenFn(scope, locals);
            watchedArray.push(disableWhen);
          }
        }
        return watchedArray;
      }),

      getOptions: function() {

        var optionItems = [];
        var selectValueMap = {};

        // The option values were already computed in the `getWatchables` fn,
        // which must have been called to trigger `getOptions`
        var optionValues = valuesFn(scope) || [];
        var optionValuesKeys = getOptionValuesKeys(optionValues);
        var optionValuesLength = optionValuesKeys.length;

        for (var index = 0; index < optionValuesLength; index++) {
          var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index];
          var value = optionValues[key];
          var locals = getLocals(value, key);
          var viewValue = viewValueFn(scope, locals);
          var selectValue = getTrackByValueFn(viewValue, locals);
          var label = displayFn(scope, locals);
          var group = groupByFn(scope, locals);
          var disabled = disableWhenFn(scope, locals);
          var optionItem = new Option(selectValue, viewValue, label, group, disabled);

          optionItems.push(optionItem);
          selectValueMap[selectValue] = optionItem;
        }

        return {
          items: optionItems,
          selectValueMap: selectValueMap,
          getOptionFromViewValue: function(value) {
            return selectValueMap[getTrackByValue(value)];
          },
          getViewValueFromOption: function(option) {
            // If the viewValue could be an object that may be mutated by the application,
            // we need to make a copy and not return the reference to the value on the option.
            return trackBy ? angular.copy(option.viewValue) : option.viewValue;
          }
        };
      }
    };
  }
@ngdoc directive
@name ngOptions
@restrict A

@description

The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
elements for the `<select>` element using the array or object obtained by evaluating the
`ngOptions` comprehension expression.

In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
similar result. However, `ngOptions` provides some benefits such as reducing memory and
increasing speed by not creating a new scope for each repeated instance, as well as providing
more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
 to a non-string value. This is because an option element can only be bound to string values at
present.

When an item in the `<select>` menu is selected, the array element or object property
represented by the selected option will be bound to the model identified by the `ngModel`
directive.

Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
be nested into the `<select>` element. This element will then represent the `null` or "not selected"
option. See example below for demonstration.

## Complex Models (objects or collections)

**Note:** By default, `ngModel` watches the model by reference, not value. This is important when
binding any input directive to a model that is an object or a collection.

Since this is a common situation for `ngOptions` the directive additionally watches the model using
`$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in
the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual
object/collection has not changed identity but only a property on the object or an item in the collection
changes.

Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection
if the model is an array). This means that changing a property deeper inside the object/collection that the
first level will not trigger a re-rendering.


## `select` **`as`**

Using `select` **`as`** will bind the result of the `select` expression to the model, but
the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
or property name (for object data sources) of the value within the collection. If a **`track by`** expression
is used, the result of that expression will be set as the value of the `option` and `select` elements.


### `select` **`as`** and **`track by`**

<div class="alert alert-warning">
Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
</div>

Consider the following example:

```html
<select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select>
```

```js
$scope.values = [{
  id: 1,
  label: 'aLabel',
  subItem: { name: 'aSubItem' }
}, {
  id: 2,
  label: 'bLabel',
  subItem: { name: 'bSubItem' }
}];

$scope.selected = { name: 'aSubItem' };
```

With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
of the data source (to `item` in this example). To calculate whether an element is selected, we do the
following:

1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
2. Apply **`track by`** to the already selected value in `ngModel`.
   In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
   value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
   a wrong object, the selected element can't be found, `<select>` is always reset to the "not
   selected" option.


@param {string} ngModel Assignable angular expression to data-bind to.
@param {string=} name Property name of the form under which the control is published.
@param {string=} required The control is considered valid only if value is entered.
@param {string=} ngRequired Adds `required` attribute and `required` validation constraint to
   the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of
   `required` when you want to data-bind to the `required` attribute.
@param {comprehension_expression=} ngOptions in one of the following forms:

  * for array data sources:
    * `label` **`for`** `value` **`in`** `array`
    * `select` **`as`** `label` **`for`** `value` **`in`** `array`
    * `label` **`group by`** `group` **`for`** `value` **`in`** `array`
    * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array`
    * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
    * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr`
    * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr`
       (for including a filter with `track by`)
  * for object data sources:
    * `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
    * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object`
    * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object`
    * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object`
    * `select` **`as`** `label` **`group by`** `group`
        **`for` `(`**`key`**`,`** `value`**`) in`** `object`
    * `select` **`as`** `label` **`disable when`** `disable`
        **`for` `(`**`key`**`,`** `value`**`) in`** `object`

Where:

  * `array` / `object`: an expression which evaluates to an array / object to iterate over.
  * `value`: local variable which will refer to each item in the `array` or each property value
     of `object` during iteration.
  * `key`: local variable which will refer to a property name in `object` during iteration.
  * `label`: The result of this expression will be the label for `<option>` element. The
    `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`).
  * `select`: The result of this expression will be bound to the model of the parent `<select>`
     element. If not specified, `select` expression will default to `value`.
  * `group`: The result of this expression will be used to group options using the `<optgroup>`
     DOM element.
  * `disable`: The result of this expression will be used to disable the rendered `<option>`
     element. Return `true` to disable.
  * `trackexpr`: Used when working with an array of objects. The result of this expression will be
     used to identify the objects in the array. The `trackexpr` will most likely refer to the
    `value` variable (e.g. `value.propertyName`). With this the selection is preserved
     even when the options are recreated (e.g. reloaded from the server).

@example
    <example module="selectExample">
      <file name="index.html">
        <script>
        angular.module('selectExample', [])
          .controller('ExampleController', ['$scope', function($scope) {
            $scope.colors = [
              {name:'black', shade:'dark'},
              {name:'white', shade:'light', notAnOption: true},
              {name:'red', shade:'dark'},
              {name:'blue', shade:'dark', notAnOption: true},
              {name:'yellow', shade:'light', notAnOption: false}
            ];
            $scope.myColor = $scope.colors[2]; // red
          }]);
        </script>
        <div ng-controller="ExampleController">
          <ul>
            <li ng-repeat="color in colors">
              <label>Name: <input ng-model="color.name"></label>
              <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label>
              <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button>
            </li>
            <li>
              <button ng-click="colors.push({})">add</button>
            </li>
          </ul>
          <hr/>
          <label>Color (null not allowed):
            <select ng-model="myColor" ng-options="color.name for color in colors"></select>
          </label><br/>
          <label>Color (null allowed):
          <span  class="nullable">
            <select ng-model="myColor" ng-options="color.name for color in colors">
              <option value="">-- choose color --</option>
            </select>
          </span></label><br/>

          <label>Color grouped by shade:
            <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors">
            </select>
          </label><br/>

          <label>Color grouped by shade, with some disabled:
            <select ng-model="myColor"
                  ng-options="color.name group by color.shade disable when color.notAnOption for color in colors">
            </select>
          </label><br/>



          Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>.
          <br/>
          <hr/>
          Currently selected: {{ {selected_color:myColor} }}
          <div style="border:solid 1px black; height:20px"
               ng-style="{'background-color':myColor.name}">
          </div>
        </div>
      </file>
      <file name="protractor.js" type="protractor">
         it('should check ng-options', function() {
           expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
           element.all(by.model('myColor')).first().click();
           element.all(by.css('select[ng-model="myColor"] option')).first().click();
           expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
           element(by.css('.nullable select[ng-model="myColor"]')).click();
           element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
           expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
         });
      </file>
    </example>