Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.DataImporter/HeuristicLab.DataImporter.Data/Model/DynamicValueList.cs @ 7968

Last change on this file since 7968 was 7267, checked in by gkronber, 13 years ago

#1734 updated copyright year in all files of the DataImporter branch

File size: 10.7 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2012 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Collections;
25using System.Linq;
26using System.Text;
27using System.Xml;
28using System.Globalization;
29using System.Linq.Expressions;
30using System.Diagnostics;
31
32namespace HeuristicLab.DataImporter.Data.Model {
33  internal class DynamicValueList : IList {
34
35    private Func<int, int, double?> expression;
36    private ColumnGroup columnGroup;
37    private int count;
38    private int recursiveIndex;
39
40    public int RecursiveIndex {
41      get { return recursiveIndex; }
42      set { recursiveIndex = value; }
43    }
44    private object syncRoot = new object();
45
46    public DynamicValueList(string expression, ColumnGroup columnGroup) {
47      this.columnGroup = columnGroup;
48      recursiveIndex = -1;
49      this.expression = Parse(expression);
50      count = columnGroup.RowCount;
51    }
52
53    public void IncreaseLength() {
54      count++;
55    }
56
57    public void DecreaseLength() {
58      count--;
59    }
60
61    #region IList Members
62
63    public int IndexOf(object item) {
64      if (!(item is double?)) return -1;
65      double? d = (double?)item;
66      for (int i = 0; i < Count; i++) {
67        if (d == (double?)this[i]) return i;
68      }
69      // not found
70      return -1;
71    }
72
73    public void Insert(int index, object item) {
74      throw new NotSupportedException();
75    }
76
77    public void RemoveAt(int index) {
78      throw new NotSupportedException();
79    }
80
81    public object this[int index] {
82      get {
83        return EvaluateExpression(index);
84      }
85      set {
86        throw new NotSupportedException();
87      }
88    }
89
90    int IList.Add(object value) {
91      throw new NotSupportedException();
92    }
93
94    public bool IsFixedSize {
95      get { return false; }
96    }
97
98    void IList.Remove(object value) {
99      throw new NotSupportedException();
100    }
101
102
103
104    private Func<int, int, double?> Parse(string expression) {
105      // make sure that every operator is separated from other tokens with a whitespace
106      // to allow splitting into tokens by whitespace
107      expression = expression.Replace("+", " + ");
108      expression = expression.Replace("-", " - ");
109      expression = expression.Replace("*", " * ");
110      expression = expression.Replace("/", " / ");
111      expression = expression.Replace("(", " ( ");
112      expression = expression.Replace(")", " ) ");
113
114      var tokens = expression.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
115      LookAheadOneEnumerator enumerator = new LookAheadOneEnumerator(tokens);
116      ParameterExpression parameter = Expression.Parameter(typeof(int), "index");
117      ParameterExpression forbiddenColumnIndexParameter = Expression.Parameter(typeof(int), "forbiddenIndex");
118      // move to first token
119      if (tokens.Length == 0) {
120        // empty expression => evaluate to missing value
121        return Expression.Lambda<Func<int, int, double?>>(Expression.Constant(null, typeof(double?)), parameter, forbiddenColumnIndexParameter).Compile();
122      } else {
123        // "index" is a free parameter to access rows of columns by index
124        // all Invoke expressions for column symbols must use the same index parameter (referentially equal)
125        // so we have to create it at root level and push it down to the terminals
126        return Expression.Lambda<Func<int, int, double?>>(ParseSimpleExpression(enumerator, parameter, forbiddenColumnIndexParameter), parameter, forbiddenColumnIndexParameter).Compile();
127      }
128    }
129
130    private Expression ParseSimpleExpression(LookAheadOneEnumerator tokens, ParameterExpression indexParameter, ParameterExpression forbiddenColumnIndexParameter) {
131      Expression term;
132      string peek = tokens.Peek;
133      if (peek == "-") {
134        tokens.MoveNext(); // move to "-" 
135        term = Expression.Negate(ParseTerm(tokens, indexParameter, forbiddenColumnIndexParameter));
136      } else if (peek == "+") {
137        tokens.MoveNext(); // move to "+"
138        term = ParseTerm(tokens, indexParameter, forbiddenColumnIndexParameter);
139      } else {
140        term = ParseTerm(tokens, indexParameter, forbiddenColumnIndexParameter);
141      }
142      while (tokens.HasNext &&
143        (tokens.Peek == "+" || tokens.Peek == "-")) {
144        tokens.MoveNext(); // move to operator
145        if (tokens.Current == "+") {
146          term = Expression.Add(term, ParseTerm(tokens, indexParameter, forbiddenColumnIndexParameter));
147        } else if (tokens.Current == "-") {
148          term = Expression.Subtract(term, ParseTerm(tokens, indexParameter, forbiddenColumnIndexParameter));
149        }
150      }
151      if (tokens.HasNext && tokens.Peek != ")") throw new ParseException("Couldn't parse the full exception.");
152      return term;
153    }
154
155    private Expression ParseTerm(LookAheadOneEnumerator tokens, ParameterExpression indexParameter, ParameterExpression forbiddenColumnIndexParameter) {
156      Expression fact = ParseFact(tokens, indexParameter, forbiddenColumnIndexParameter);
157      while (tokens.HasNext &&
158        (tokens.Peek == "*" || tokens.Peek == "/")) {
159        tokens.MoveNext(); // set to operator
160        if (tokens.Current == "*") {
161          fact = Expression.Multiply(fact, ParseFact(tokens, indexParameter, forbiddenColumnIndexParameter));
162        } else if (tokens.Current == "/") {
163          fact = Expression.Divide(fact, ParseFact(tokens, indexParameter, forbiddenColumnIndexParameter));
164        }
165      }
166      return fact;
167    }
168
169    private Expression ParseFact(LookAheadOneEnumerator tokens, ParameterExpression indexParameter, ParameterExpression forbiddenColumnIndexParameter) {
170      Expression fact;
171      if (tokens.Peek == "(") {
172        tokens.MoveNext(); // set to "("
173        fact = ParseSimpleExpression(tokens, indexParameter, forbiddenColumnIndexParameter);
174        // expect ")"
175        if (!(tokens.HasNext && tokens.Peek == ")")) throw new ParseException(@""")"" expected instead of """ + tokens + @""".");
176        tokens.MoveNext(); // set to ")"
177        return fact;
178      } else {
179        double c;
180        int columnIndex;
181        tokens.MoveNext(); // set to terminal
182        if (double.TryParse(tokens.Current, out c)) {
183          return Expression.Constant(c, typeof(double?));
184        } else if (TryTranslateColumnIndex(tokens.Current.ToUpper(), out columnIndex)) {
185          Expression<Func<int, int, double?>> readValueExp =
186            (index, forbiddenIndex) =>
187              (columnIndex < columnGroup.Columns.Count() &&
188               columnIndex != forbiddenIndex &&
189               columnGroup.GetColumn(columnIndex).DataType == typeof(double?) &&
190               index < columnGroup.GetColumn(columnIndex).TotalValuesCount) ?
191              (double?)columnGroup.GetColumn(columnIndex).GetValue(index) : null;
192          return Expression.Invoke(readValueExp, indexParameter, forbiddenColumnIndexParameter);
193        } else {
194          throw new ParseException("Unknown terminal: " + tokens.Current);
195        }
196      }
197    }
198
199    private bool TryTranslateColumnIndex(string symb, out int columnIndex) {
200      if (symb.Length == 1) {       // 'A' .. 'Z'
201        return TryTranslateColumnIndexDigit(symb[0], out columnIndex);
202      } else if (symb.Length == 2) { // 'AA' ... 'ZZ'
203        bool ok;
204        int d0, d1;
205        ok = TryTranslateColumnIndexDigit(symb[0], out d1) & TryTranslateColumnIndexDigit(symb[1], out d0);
206        columnIndex = (d1 + 1) * 26 + d0;
207        return ok;
208      } else {
209        columnIndex = 0;
210        return false;
211      }
212    }
213
214    private bool TryTranslateColumnIndexDigit(char d, out int columnIndex) {
215      if (d < 'A' || d > 'Z') {
216        columnIndex = 0;
217        return false;
218      } else {
219        columnIndex = d - 'A';
220        return true;
221      }
222    }
223
224    private double? EvaluateExpression(int index) {
225      return expression(index, recursiveIndex);
226    }
227
228    #endregion
229
230    #region ICollection Members
231
232    public void Add(object item) {
233      throw new NotSupportedException();
234    }
235
236    public void Clear() {
237      throw new NotSupportedException();
238    }
239
240    public bool Contains(object item) {
241      return IndexOf(item) != -1;
242    }
243
244    public int Count {
245      get { return count; }
246    }
247
248    public bool IsReadOnly {
249      get { return true; }
250    }
251
252    public bool Remove(object item) {
253      throw new NotSupportedException();
254    }
255
256    public void CopyTo(Array array, int index) {
257      for (int i = index; i < array.Length && i < Count; i++) {
258        array.SetValue(this[i], i);
259      }
260    }
261
262    public bool IsSynchronized {
263      get { return false; }
264    }
265
266    public object SyncRoot {
267      get { return syncRoot; }
268    }
269
270    #endregion
271
272    #region IEnumerable Members
273
274    IEnumerator IEnumerable.GetEnumerator() {
275      for (int i = 0; i < Count; i++)
276        yield return this[i];
277    }
278
279    #endregion
280
281    // private enumerator class with one lookahead symbol (Peek) needed to parse expressions
282    private class LookAheadOneEnumerator {
283      private string[] tokens;
284      private int index;
285
286      public LookAheadOneEnumerator(string[] tokens) {
287        this.tokens = tokens;
288        index = -1;
289      }
290
291      public void MoveNext() {
292        if (HasNext) index++;
293        else throw new InvalidOperationException("End of the token stream");
294      }
295
296      public bool HasNext {
297        get { return index < tokens.Length - 1; }
298      }
299      public string Current {
300        get {
301          if (index < 0) throw new InvalidOperationException("Not initialized. Call MoveNext() first");
302          else return tokens[index];
303        }
304      }
305      public string Peek {
306        get {
307          if (HasNext) {
308            return tokens[index + 1];
309          } else
310            return "";
311        }
312      }
313    }
314  }
315
316  public class ParseException : Exception {
317    public ParseException(string message) : base(message) { }
318  }
319}
Note: See TracBrowser for help on using the repository browser.