23 }
24 }
25 return result;
26 }
Функция evalTerm() очень напоминает функцию evalExpression(), но, в отличие от последней, она имеет дело с операциями умножения и деления. В функции evalTerm() необходимо учитывать одну тонкость, а именно: нельзя допускать деления на нуль, так как это приводит к ошибке на некоторых процессорах. Хотя не рекомендуется проверять равенство чисел с плавающей точкой из-за ошибки округления, можно спокойно делать проверку на равенство значению 0.0 для предотвращения деления на нуль.
01 QVariant Celclass="underline" :evalFactor(const QString &str, int &pos) const
02 {
03 QVariant result;
04 bool negative = false;
05 if (str[pos] == '-') {
06 negative = true;
07 ++pos;
08 }
09 if (str[pos] == '(') {
10 ++pos;
11 result = evalExpression(str, pos);
12 if (str[pos] != ')')
13 result = Invalid;
14 ++pos;
15 } else {
16 QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}");
17 QString token;
18 while (str[pos].isLetterOrNumber() || str[pos] == '.') {
19 token += str[pos];
20 ++pos;
21 }
22 if (regExp.exactMatch(token)) {
23 int column = token[0].toUpper().unicode() - 'A';
24 int row = token.mid(1).toInt() - 1;
25 Cell *c = static_cast<Cell *>(tableWidget()->item(row, column));
26 if (c) {
27 result = c->value();
28 } else {
29 result = 0.0;
30 }
31 } else {
32 bool ok;
33 result = token.toDouble(&ok);
34 if (!ok)
35 result = Invalid;
36 }
37 }
38 if (negative) {
39 if (result.type() == QVariant::Double) {
40 result = -result.toDouble();
41 } else {
42 result = Invalid;
43 }
44 }
45 return result;
46 }
Функция evalFactor() немного сложнее, чем evalExpression() и evalTerm(). Мы начинаем с проверки, не является ли фактор отрицательным. Затем мы проверяем наличие открытой скобки. Если она имеется, мы анализируем значение внутри скобок как выражение, вызывая evalExpression(). При анализе выражения в скобках evalExpression() вызывает функцию evalTerm(), которая вызывает функцию evalFactor(), которая вновь вызывает функцию evalExpression(). Именно в этом месте осуществляется рекурсия при синтаксическом анализе.
Если фактором не является вложенное выражение, мы выделяем следующую лексему (token), и она должна задавать обозначение ячейки или быть числом. Если эта лексема удовлетворяет регулярному выражению в переменной QRegExp, мы считаем, что она является ссылкой на ячейку, и вызываем функцию value() для этой ячейки. Ячейка может располагаться в любом месте в электронной таблице, и она может ссылаться на другие ячейки. Такая зависимость не вызывает проблемы и просто приводит к дополнительным вызовам функции value() и к дополнительному синтаксическому анализу ячеек с признаком «dirty» («грязный») для перерасчета значений всех зависимых ячеек. Если лексема не является ссылкой на ячейку, мы рассматриваем ее как число.
Что произойдет, если ячейка A1 содержит формулу «=A1»? Или если ячейка A1 содержит «=A2», а ячейка A2 содержит «=A1»? Хотя нами не написан специальный программный код для обнаружения бесконечных циклов в рекурсивных зависимостях, парсер прекрасно справится с этой ситуацией и возвратит недопустимое значение переменной типа QVariant. Это даст нужный результат, поскольку мы устанавливаем флажок cacheIsDirty на значение false и переменную cachedValue на значение Invalid в функции value() перед вызовом evalExpression(). Если evalExpression() рекурсивно вызывает функцию value() для той же ячейки, она немедленно возвращает значение Invalid, и тогда все выражение принимает значение Invalid.
Теперь мы завершили программу синтаксического анализа формул. Ее можно легко модифицировать для обработки стандартных функций электронной таблицы, например «sum()» и «avg()», расширяя грамматическое определение фактора. Можно также легко расширить эту реализацию, обеспечив возможность выполнения операции «+» над строковыми операндами (для их конкатенации); это не потребует внесения изменений в грамматику.