32.1.1
by mh21 at piware
Initial version with expression support. |
1 |
/******************************************************************************
|
87
by Michael Hofmann
Fix license headers. |
2 |
* Copyright (C) 2011-2013 Michael Hofmann <mh21@mh21.de> *
|
32.1.1
by mh21 at piware
Initial version with expression support. |
3 |
* *
|
4 |
* This program is free software; you can redistribute it and/or modify *
|
|
5 |
* it under the terms of the GNU General Public License as published by *
|
|
6 |
* the Free Software Foundation; either version 3 of the License, or *
|
|
7 |
* (at your option) any later version. *
|
|
8 |
* *
|
|
9 |
* This program is distributed in the hope that it will be useful, *
|
|
10 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
11 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
12 |
* GNU General Public License for more details. *
|
|
13 |
* *
|
|
14 |
* You should have received a copy of the GNU General Public License along *
|
|
15 |
* with this program; if not, write to the Free Software Foundation, Inc., *
|
|
16 |
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
|
|
17 |
******************************************************************************/
|
|
18 |
||
46
by Michael Hofmann
Code cleanups. |
19 |
internal class ExpressionTokenizer { |
20 |
private char *last; |
|
21 |
private char *current; |
|
22 |
private string[] result; |
|
32.1.6
by mh21 at piware
Real parser. |
23 |
|
32.1.1
by mh21 at piware
Initial version with expression support. |
24 |
public string[] tokenize(string expression) { |
46
by Michael Hofmann
Code cleanups. |
25 |
this.result = null; |
26 |
this.last = null; |
|
32.1.1
by mh21 at piware
Initial version with expression support. |
27 |
int level = 0; |
28 |
bool inexpression = false; |
|
52
by Michael Hofmann
Tokenize strings. |
29 |
bool instringsingle = false; |
30 |
bool instringdouble = false; |
|
97
by Michael Hofmann
Move settings to /de/mh21/indicator-multiload because of deprecation of /apps. |
31 |
for (this.current = (char*)expression; *this.current != '\0'; |
46
by Michael Hofmann
Code cleanups. |
32 |
this.current = this.current + 1) { |
32.1.1
by mh21 at piware
Initial version with expression support. |
33 |
if (!inexpression) { |
46
by Michael Hofmann
Code cleanups. |
34 |
if (*this.current == '$') { |
35 |
this.save(); |
|
52
by Michael Hofmann
Tokenize strings. |
36 |
this.addcurrent(); |
32.1.1
by mh21 at piware
Initial version with expression support. |
37 |
inexpression = true; |
38 |
} else { |
|
46
by Michael Hofmann
Code cleanups. |
39 |
this.expand(); |
32.1.1
by mh21 at piware
Initial version with expression support. |
40 |
}
|
52
by Michael Hofmann
Tokenize strings. |
41 |
continue; |
42 |
}
|
|
43 |
// inexpression
|
|
44 |
if (level == 0) { |
|
45 |
if (this.isvariable()) { |
|
46 |
this.expand(); |
|
47 |
} else if (this.last == null && *this.current == '(') { |
|
48 |
this.addcurrent(); |
|
49 |
++level; |
|
50 |
} else { |
|
51 |
this.add('('); |
|
52 |
this.save(); |
|
53 |
this.add(')'); |
|
54 |
this.expand(); |
|
55 |
inexpression = false; |
|
56 |
}
|
|
57 |
continue; |
|
58 |
}
|
|
59 |
// level > 0
|
|
60 |
if (instringsingle) { |
|
61 |
this.expand(); |
|
62 |
if (*this.current == '\'') { |
|
63 |
this.savewithcurrent(); |
|
64 |
instringsingle = false; |
|
65 |
}
|
|
66 |
continue; |
|
67 |
}
|
|
68 |
if (instringdouble) { |
|
69 |
this.expand(); |
|
70 |
if (*this.current == '"') { |
|
71 |
this.savewithcurrent(); |
|
72 |
instringdouble = false; |
|
73 |
}
|
|
74 |
continue; |
|
75 |
}
|
|
76 |
// !instring
|
|
77 |
if (*this.current == '\'') { |
|
78 |
this.save(); |
|
79 |
this.expand(); |
|
80 |
instringsingle = true; |
|
81 |
} else if (*this.current == '"') { |
|
82 |
this.save(); |
|
83 |
this.expand(); |
|
84 |
instringdouble = true; |
|
85 |
} else if (*this.current == '(') { |
|
86 |
this.save(); |
|
87 |
this.addcurrent(); |
|
88 |
++level; |
|
89 |
} else if (*this.current == ')') { |
|
90 |
this.save(); |
|
91 |
this.addcurrent(); |
|
92 |
--level; |
|
93 |
if (level == 0) |
|
94 |
inexpression = false; |
|
95 |
} else if (this.isspace()) { |
|
96 |
this.save(); |
|
97 |
} else if (!this.isvariable()) { |
|
98 |
this.save(); |
|
99 |
this.addcurrent(); |
|
32.1.1
by mh21 at piware
Initial version with expression support. |
100 |
} else { |
52
by Michael Hofmann
Tokenize strings. |
101 |
this.expand(); |
32.1.1
by mh21 at piware
Initial version with expression support. |
102 |
}
|
103 |
}
|
|
52
by Michael Hofmann
Tokenize strings. |
104 |
// fixup open strings, parentheses
|
105 |
if (instringdouble) |
|
106 |
this.savewith('"'); |
|
107 |
else if (instringsingle) |
|
108 |
this.savewith('\''); |
|
109 |
else
|
|
110 |
this.save(); |
|
111 |
while (level > 0) { |
|
112 |
this.add(')'); |
|
113 |
--level; |
|
114 |
}
|
|
46
by Michael Hofmann
Code cleanups. |
115 |
return this.result; |
116 |
}
|
|
117 |
||
118 |
private void expand() { |
|
119 |
if (this.last == null) |
|
120 |
this.last = this.current; |
|
121 |
// stderr.printf("Expanding token to '%s'\n", strndup(last, current - last + 1));
|
|
122 |
}
|
|
123 |
||
52
by Michael Hofmann
Tokenize strings. |
124 |
// add the current character as a new token
|
125 |
private void addcurrent() { |
|
126 |
this.add(*this.current); |
|
127 |
}
|
|
128 |
||
129 |
// add a character as a new token
|
|
130 |
private void add(char what) { |
|
131 |
var token = what.to_string(); |
|
132 |
// stderr.printf("Adding token '%s'\n", token);
|
|
133 |
this.result += token; |
|
134 |
}
|
|
135 |
||
136 |
// if the current token is not empty, push it to the token list and set the
|
|
137 |
// current token to empty; this will not include the current character
|
|
46
by Michael Hofmann
Code cleanups. |
138 |
private void save() { |
139 |
if (this.last != null) { |
|
140 |
var token = strndup(this.last, this.current - this.last); |
|
141 |
// stderr.printf("Saving token '%s'\n", token);
|
|
142 |
this.result += token; |
|
143 |
this.last = null; |
|
144 |
} else { |
|
145 |
// stderr.printf("Not saving empty token\n");
|
|
146 |
}
|
|
147 |
}
|
|
148 |
||
52
by Michael Hofmann
Tokenize strings. |
149 |
private void savewithcurrent() { |
150 |
this.savewith(*this.current); |
|
151 |
}
|
|
152 |
||
153 |
// add a character to the current token, push it to the token list
|
|
154 |
// and set the current token to empty
|
|
155 |
private void savewith(char what) { |
|
156 |
string token = (this.last != null ? |
|
157 |
strndup(this.last, this.current - this.last) : "") + what.to_string(); |
|
58
by Michael Hofmann
Optimize expression parsing. |
158 |
// stderr.printf("Saving token '%s'\n", token);
|
46
by Michael Hofmann
Code cleanups. |
159 |
this.result += token; |
52
by Michael Hofmann
Tokenize strings. |
160 |
this.last = null; |
46
by Michael Hofmann
Code cleanups. |
161 |
}
|
162 |
||
163 |
private bool isspace() { |
|
164 |
return *this.current == ' '; |
|
165 |
}
|
|
166 |
||
167 |
private bool isvariable() { |
|
168 |
return
|
|
169 |
*this.current >= 'a' && *this.current <= 'z' || |
|
170 |
*this.current >= '0' && *this.current <= '9' || |
|
171 |
*this.current == '.'; |
|
172 |
}
|
|
173 |
}
|
|
174 |
||
175 |
internal class ExpressionEvaluator { |
|
176 |
private Providers providers; |
|
177 |
||
178 |
private uint index; |
|
179 |
private string[] tokens; |
|
180 |
||
181 |
public ExpressionEvaluator(Providers providers) { |
|
182 |
this.providers = providers; |
|
183 |
}
|
|
184 |
||
185 |
private static Error error(uint index, string message) { |
|
186 |
return new Error(Quark.from_string("expression-error-quark"), |
|
187 |
(int)index, "%s", message); |
|
188 |
}
|
|
189 |
||
190 |
private string parens_or_identifier() throws Error { |
|
191 |
if (this.index >= this.tokens.length) |
|
192 |
throw error(this.index, "empty expression"); |
|
193 |
if (this.tokens[this.index] == "(") |
|
194 |
return parens(); |
|
195 |
return identifier(); |
|
196 |
}
|
|
197 |
||
198 |
private string times() throws Error { |
|
32.1.6
by mh21 at piware
Real parser. |
199 |
string result = null; |
200 |
bool div = false; |
|
201 |
for (;;) { |
|
46
by Michael Hofmann
Code cleanups. |
202 |
if (this.index >= this.tokens.length) |
203 |
throw error(this.index, "expression expected"); |
|
204 |
var value = parens_or_identifier(); |
|
32.1.6
by mh21 at piware
Real parser. |
205 |
if (result == null) |
206 |
result = value; |
|
207 |
else if (!div) |
|
208 |
result = (double.parse(result) * double.parse(value)).to_string(); |
|
209 |
else
|
|
210 |
result = (double.parse(result) / double.parse(value)).to_string(); |
|
46
by Michael Hofmann
Code cleanups. |
211 |
if (this.index >= this.tokens.length) |
32.1.6
by mh21 at piware
Real parser. |
212 |
return result; |
46
by Michael Hofmann
Code cleanups. |
213 |
switch (this.tokens[this.index]) { |
32.1.6
by mh21 at piware
Real parser. |
214 |
case "*": |
215 |
div = false; |
|
46
by Michael Hofmann
Code cleanups. |
216 |
++this.index; |
32.1.6
by mh21 at piware
Real parser. |
217 |
continue; |
218 |
case "/": |
|
219 |
div = true; |
|
46
by Michael Hofmann
Code cleanups. |
220 |
++this.index; |
32.1.6
by mh21 at piware
Real parser. |
221 |
continue; |
222 |
default: |
|
223 |
return result; |
|
224 |
}
|
|
32.1.5
by mh21 at piware
Expression parser work. |
225 |
}
|
226 |
}
|
|
227 |
||
46
by Michael Hofmann
Code cleanups. |
228 |
private string plus() throws Error { |
32.1.5
by mh21 at piware
Expression parser work. |
229 |
string result = null; |
230 |
bool minus = false; |
|
231 |
for (;;) { |
|
46
by Michael Hofmann
Code cleanups. |
232 |
if (this.index >= this.tokens.length) |
233 |
throw error(this.index, "expression expected"); |
|
234 |
var value = times(); |
|
32.1.6
by mh21 at piware
Real parser. |
235 |
if (result == null) |
32.1.5
by mh21 at piware
Expression parser work. |
236 |
result = value; |
32.1.6
by mh21 at piware
Real parser. |
237 |
else if (!minus) |
238 |
result = (double.parse(result) + double.parse(value)).to_string(); |
|
32.1.5
by mh21 at piware
Expression parser work. |
239 |
else
|
32.1.6
by mh21 at piware
Real parser. |
240 |
result = (double.parse(result) - double.parse(value)).to_string(); |
46
by Michael Hofmann
Code cleanups. |
241 |
if (this.index >= this.tokens.length) |
32.1.5
by mh21 at piware
Expression parser work. |
242 |
return result; |
46
by Michael Hofmann
Code cleanups. |
243 |
switch (this.tokens[this.index]) { |
32.1.6
by mh21 at piware
Real parser. |
244 |
case "+": |
32.1.5
by mh21 at piware
Expression parser work. |
245 |
minus = false; |
46
by Michael Hofmann
Code cleanups. |
246 |
++this.index; |
32.1.5
by mh21 at piware
Expression parser work. |
247 |
continue; |
32.1.6
by mh21 at piware
Real parser. |
248 |
case "-": |
32.1.5
by mh21 at piware
Expression parser work. |
249 |
minus = true; |
46
by Michael Hofmann
Code cleanups. |
250 |
++this.index; |
32.1.5
by mh21 at piware
Expression parser work. |
251 |
continue; |
32.1.6
by mh21 at piware
Real parser. |
252 |
default: |
253 |
return result; |
|
32.1.5
by mh21 at piware
Expression parser work. |
254 |
}
|
255 |
}
|
|
256 |
}
|
|
257 |
||
46
by Michael Hofmann
Code cleanups. |
258 |
private string parens() throws Error { |
259 |
if (this.index >= this.tokens.length || |
|
260 |
this.tokens[this.index] != "(") |
|
261 |
throw error(this.index, "'(' expected"); |
|
262 |
++this.index; |
|
263 |
var result = plus(); |
|
264 |
if (this.index >= this.tokens.length || |
|
265 |
this.tokens[this.index] != ")") |
|
266 |
throw error(this.index, "')' expected"); |
|
267 |
++this.index; |
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
268 |
return result; |
269 |
}
|
|
270 |
||
46
by Michael Hofmann
Code cleanups. |
271 |
private string[] params() throws Error { |
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
272 |
string[] result = null; |
46
by Michael Hofmann
Code cleanups. |
273 |
if (this.index >= this.tokens.length || |
274 |
this.tokens[this.index] != "(") |
|
275 |
throw error(this.index, "'(' expected"); |
|
276 |
++this.index; |
|
277 |
if (this.index >= this.tokens.length) |
|
278 |
throw error(this.index, "parameters expected"); |
|
279 |
if (this.tokens[this.index] != ")") { |
|
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
280 |
for (;;) { |
46
by Michael Hofmann
Code cleanups. |
281 |
result += plus(); |
282 |
if (this.index >= this.tokens.length) |
|
283 |
throw error(this.index, "')' expected"); |
|
284 |
if (this.tokens[this.index] != ",") |
|
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
285 |
break; |
46
by Michael Hofmann
Code cleanups. |
286 |
++this.index; |
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
287 |
}
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
288 |
}
|
46
by Michael Hofmann
Code cleanups. |
289 |
if (this.index >= this.tokens.length || |
290 |
this.tokens[this.index] != ")") |
|
291 |
throw error(this.index, "')' expected"); |
|
292 |
++this.index; |
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
293 |
return result; |
294 |
}
|
|
295 |
||
46
by Michael Hofmann
Code cleanups. |
296 |
private string identifier() throws Error { |
297 |
if (this.index >= this.tokens.length) |
|
298 |
throw error(this.index, "identifier expected"); |
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
299 |
double sign = 1; |
46
by Michael Hofmann
Code cleanups. |
300 |
if (this.tokens[this.index] == "+") { |
301 |
++this.index; |
|
302 |
if (this.index >= this.tokens.length) |
|
303 |
throw error(this.index, "identifier expected"); |
|
304 |
} else if (this.tokens[this.index] == "-") { |
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
305 |
sign = -1.0; |
46
by Michael Hofmann
Code cleanups. |
306 |
++this.index; |
307 |
if (this.index >= this.tokens.length) |
|
308 |
throw error(this.index, "identifier expected"); |
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
309 |
}
|
46
by Michael Hofmann
Code cleanups. |
310 |
var token = this.tokens[this.index]; |
54
by Michael Hofmann
Split function in their own class. |
311 |
if (token.length > 0 && (token[0] == '\'' || token[0] == '"')) { |
312 |
++this.index; |
|
313 |
return (sign == -1 ? "-" : "") + token[1:token.length - 1]; |
|
314 |
}
|
|
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
315 |
if (token.length > 0 && (token[0] >= '0' && token[0] <= '9' || token[0] == '.')) { |
46
by Michael Hofmann
Code cleanups. |
316 |
++this.index; |
32.1.7
by mh21 at piware
Numbers and multiple arguments for functions in expressions. |
317 |
if (sign == -1) |
318 |
return "-" + token; |
|
319 |
return token; |
|
320 |
}
|
|
123
by Michael Hofmann
Support for variables like cpu0.idle. Net provider vars net.*.{down,up}. |
321 |
var varparts = token.split(".", 2); |
46
by Michael Hofmann
Code cleanups. |
322 |
var nameindex = this.index; |
323 |
++this.index; |
|
32.1.6
by mh21 at piware
Real parser. |
324 |
switch (varparts.length) { |
325 |
case 1: |
|
54
by Michael Hofmann
Split function in their own class. |
326 |
bool found = false; |
327 |
try { |
|
55
by Michael Hofmann
Automatic widest strings for indicator text. |
328 |
var result = this.providers.call(token, this.params(), |
137
by mh21 at mh21
Remove support for indicator items. |
329 |
out found); |
54
by Michael Hofmann
Split function in their own class. |
330 |
if (!found) |
331 |
throw error(nameindex, "unknown function"); |
|
332 |
return (sign == -1 ? "-" : "") + result; |
|
333 |
} catch (Error e) { |
|
334 |
// TODO: this is not the right error position, maybe it is one
|
|
335 |
// of the parameters so how to we transport this, maybe add
|
|
336 |
// nameindex + e.errorcode?
|
|
337 |
throw error(nameindex, e.message); |
|
32.1.6
by mh21 at piware
Real parser. |
338 |
}
|
339 |
case 2: |
|
52
by Michael Hofmann
Tokenize strings. |
340 |
bool found = false; |
341 |
var result = (sign * this.providers.value(token, out found)).to_string(); |
|
342 |
if (!found) |
|
343 |
throw error(nameindex, "unknown variable"); |
|
344 |
return result; |
|
32.1.6
by mh21 at piware
Real parser. |
345 |
default: |
123
by Michael Hofmann
Support for variables like cpu0.idle. Net provider vars net.*.{down,up}. |
346 |
// not reached
|
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
347 |
throw error(nameindex, "too many identifier parts"); |
32.1.6
by mh21 at piware
Real parser. |
348 |
}
|
349 |
}
|
|
350 |
||
46
by Michael Hofmann
Code cleanups. |
351 |
private string text() throws Error { |
43
by Michael Hofmann
Small cmdline fixes. |
352 |
string[] result = {}; |
46
by Michael Hofmann
Code cleanups. |
353 |
while (this.index < this.tokens.length) { |
354 |
string current = this.tokens[this.index]; |
|
32.1.6
by mh21 at piware
Real parser. |
355 |
if (current == "$") { |
46
by Michael Hofmann
Code cleanups. |
356 |
++this.index; |
357 |
result += parens_or_identifier(); |
|
32.1.6
by mh21 at piware
Real parser. |
358 |
} else { |
359 |
result += current; |
|
46
by Michael Hofmann
Code cleanups. |
360 |
++this.index; |
32.1.6
by mh21 at piware
Real parser. |
361 |
}
|
362 |
}
|
|
363 |
||
364 |
return string.joinv("", result); |
|
32.1.5
by mh21 at piware
Expression parser work. |
365 |
}
|
366 |
||
137
by mh21 at mh21
Remove support for indicator items. |
367 |
public string evaluate(string[] tokens) { |
46
by Michael Hofmann
Code cleanups. |
368 |
this.index = 0; |
369 |
this.tokens = tokens; |
|
32.1.6
by mh21 at piware
Real parser. |
370 |
try { |
46
by Michael Hofmann
Code cleanups. |
371 |
return text(); |
32.1.6
by mh21 at piware
Real parser. |
372 |
} catch (Error e) { |
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
373 |
stderr.printf("Expression error: %s\n", e.message); |
374 |
string errormessage = ""; |
|
375 |
int errorpos = -1; |
|
46
by Michael Hofmann
Code cleanups. |
376 |
for (uint i = 0, isize = this.tokens.length; i < isize; ++i) { |
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
377 |
if (e.code == i) |
378 |
errorpos = errormessage.length; |
|
46
by Michael Hofmann
Code cleanups. |
379 |
errormessage += " " + this.tokens[i]; |
32.1.12
by mh21 at piware
Reasonable error messages for expressions on stderr. |
380 |
}
|
381 |
if (errorpos < 0) |
|
382 |
errorpos = errormessage.length; |
|
383 |
stderr.printf("%s\n%s^\n", errormessage, string.nfill(errorpos, '-')); |
|
32.1.6
by mh21 at piware
Real parser. |
384 |
return ""; |
32.1.1
by mh21 at piware
Initial version with expression support. |
385 |
}
|
386 |
}
|
|
387 |
}
|
|
388 |
||
58
by Michael Hofmann
Optimize expression parsing. |
389 |
public class ExpressionCache : Object { |
390 |
public Providers providers { get; construct; } |
|
391 |
||
392 |
private string[] _tokens; |
|
393 |
private string _label; |
|
394 |
||
395 |
private string _expression; |
|
396 |
public string expression { |
|
397 |
get { |
|
398 |
return this._expression; |
|
399 |
}
|
|
400 |
construct set { |
|
401 |
this._expression = value; |
|
402 |
this._tokens = null; |
|
403 |
this._label = null; |
|
404 |
}
|
|
405 |
}
|
|
406 |
||
407 |
public ExpressionCache(Providers providers, string expression) { |
|
408 |
Object(providers: providers, expression: expression); |
|
409 |
}
|
|
410 |
||
411 |
public void update() { |
|
412 |
this._label = null; |
|
413 |
}
|
|
414 |
||
415 |
public string[] tokens() { |
|
416 |
if (this._tokens == null) |
|
417 |
this._tokens = new ExpressionTokenizer().tokenize(this._expression); |
|
418 |
return this._tokens; |
|
419 |
}
|
|
420 |
||
421 |
public string label() { |
|
422 |
if (this._label == null) |
|
137
by mh21 at mh21
Remove support for indicator items. |
423 |
this._label = new ExpressionEvaluator(this.providers).evaluate(this.tokens()); |
58
by Michael Hofmann
Optimize expression parsing. |
424 |
return this._label; |
425 |
}
|
|
46
by Michael Hofmann
Code cleanups. |
426 |
}
|
427 |