~indicator-multiload/indicator-multiload/trunk

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