Free cookie consent management tool by TermsFeed Policy Generator

source: addons/HeuristicLab.DataImporter/HeuristicLab.DataImporter.Data/Model/DynamicValueList.cs @ 17717

Last change on this file since 17717 was 16994, checked in by gkronber, 6 years ago

#2520 Update plugin dependencies and references for HL.DataImporter for new persistence

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