1// Copyright 2014 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13// limitations under the License.
14
15(function(scope) {
16
17  // consume* functions return a 2 value array of [parsed-data, '' or not-yet consumed input]
18
19  // Regex should be anchored with /^
20  function consumeToken(regex, string) {
21    var result = regex.exec(string);
22    if (result) {
23      result = regex.ignoreCase ? result[0].toLowerCase() : result[0];
24      return [result, string.substr(result.length)];
25    }
26  }
27
28  function consumeTrimmed(consumer, string) {
29    string = string.replace(/^\s*/, '');
30    var result = consumer(string);
31    if (result) {
32      return [result[0], result[1].replace(/^\s*/, '')];
33    }
34  }
35
36  function consumeRepeated(consumer, separator, string) {
37    consumer = consumeTrimmed.bind(null, consumer);
38    var list = [];
39    while (true) {
40      var result = consumer(string);
41      if (!result) {
42        return [list, string];
43      }
44      list.push(result[0]);
45      string = result[1];
46      result = consumeToken(separator, string);
47      if (!result || result[1] == '') {
48        return [list, string];
49      }
50      string = result[1];
51    }
52  }
53
54  // Consumes a token or expression with balanced parentheses
55  function consumeParenthesised(parser, string) {
56    var nesting = 0;
57    for (var n = 0; n < string.length; n++) {
58      if (/\s|,/.test(string[n]) && nesting == 0) {
59        break;
60      } else if (string[n] == '(') {
61        nesting++;
62      } else if (string[n] == ')') {
63        nesting--;
64        if (nesting == 0)
65          n++;
66        if (nesting <= 0)
67          break;
68      }
69    }
70    var parsed = parser(string.substr(0, n));
71    return parsed == undefined ? undefined : [parsed, string.substr(n)];
72  }
73
74  function lcm(a, b) {
75    var c = a;
76    var d = b;
77    while (c && d)
78      c > d ? c %= d : d %= c;
79    c = (a * b) / (c + d);
80    return c;
81  }
82
83  function ignore(value) {
84    return function(input) {
85      var result = value(input);
86      if (result)
87        result[0] = undefined;
88      return result;
89    }
90  }
91
92  function optional(value, defaultValue) {
93    return function(input) {
94      var result = value(input);
95      if (result)
96        return result;
97      return [defaultValue, input];
98    }
99  }
100
101  function consumeList(list, input) {
102    var output = [];
103    for (var i = 0; i < list.length; i++) {
104      var result = scope.consumeTrimmed(list[i], input);
105      if (!result || result[0] == '')
106        return;
107      if (result[0] !== undefined)
108        output.push(result[0]);
109      input = result[1];
110    }
111    if (input == '') {
112      return output;
113    }
114  }
115
116  function mergeWrappedNestedRepeated(wrap, nestedMerge, separator, left, right) {
117    var matchingLeft = [];
118    var matchingRight = [];
119    var reconsititution = [];
120    var length = lcm(left.length, right.length);
121    for (var i = 0; i < length; i++) {
122      var thing = nestedMerge(left[i % left.length], right[i % right.length]);
123      if (!thing) {
124        return;
125      }
126      matchingLeft.push(thing[0]);
127      matchingRight.push(thing[1]);
128      reconsititution.push(thing[2]);
129    }
130    return [matchingLeft, matchingRight, function(positions) {
131      var result = positions.map(function(position, i) {
132        return reconsititution[i](position);
133      }).join(separator);
134      return wrap ? wrap(result) : result;
135    }];
136  }
137
138  function mergeList(left, right, list) {
139    var lefts = [];
140    var rights = [];
141    var functions = [];
142    var j = 0;
143    for (var i = 0; i < list.length; i++) {
144      if (typeof list[i] == 'function') {
145        var result = list[i](left[j], right[j++]);
146        lefts.push(result[0]);
147        rights.push(result[1]);
148        functions.push(result[2]);
149      } else {
150        (function(pos) {
151          lefts.push(false);
152          rights.push(false);
153          functions.push(function() { return list[pos]; });
154        })(i);
155      }
156    }
157    return [lefts, rights, function(results) {
158      var result = '';
159      for (var i = 0; i < results.length; i++) {
160        result += functions[i](results[i]);
161      }
162      return result;
163    }];
164  }
165
166  scope.consumeToken = consumeToken;
167  scope.consumeTrimmed = consumeTrimmed;
168  scope.consumeRepeated = consumeRepeated;
169  scope.consumeParenthesised = consumeParenthesised;
170  scope.ignore = ignore;
171  scope.optional = optional;
172  scope.consumeList = consumeList;
173  scope.mergeNestedRepeated = mergeWrappedNestedRepeated.bind(null, null);
174  scope.mergeWrappedNestedRepeated = mergeWrappedNestedRepeated;
175  scope.mergeList = mergeList;
176
177})(webAnimations1);
178