Благодаря заданному в функции toDouble() указателю на булево значение мы можем отличать строку преобразования, представляющую числовое значение 0.0, от ошибки преобразования (в последнем случае также возвращается 0.0, но булева переменная устанавливается в значение false). Иногда нулевое значение при неудачном преобразовании оказывается именно тем, что нам нужно; в этом случае нет необходимости передавать указать на переменную типа bool. По причинам, связанным с производительностью и переносимостью, в Qt никогда не используются исключения С++ для вывода сообщений об ошибках. Это не значит, что вы не можете использовать их в своих Qt-программах, если ваш компилятор поддерживает исключения С++.
Функция value() объявлена с модификатором const. При объявлении переменных cachedValue и cacheIsValid мы использовали ключевое слово mutable, чтобы компилятор позволял нам модифицировать эти переменные в функциях типа const. Может показаться заманчивой возможность сделать функцию value() не типа const и удалить ключевые слова mutable, но это не пропустит компилятор, поскольку мы вызываем value() из data() - функции с модификатором const.
Теперь можно считать, что мы завершили приложение Электронная таблица, если не брать в расчет синтаксический анализ формул. В остальной части данного раздела рассматриваются функция evalExpression() и две вспомогательные функции evalTerm() и evalFactor(). Их программный код немного сложен, но он включен сюда, чтобы приложение имело законченный вид. Поскольку этот программный код не относится к программированию графического интерфейса, вы можете спокойно его пропустить и продолжить чтение с главы 5.
Функция evalExpression() возвращает значение выражения из ячейки электронной таблицы. Выражение состоит из одного или нескольких термов, разделенных знаками операций "+" или "-". Термы состоят из одного или нескольких факторов (factors), разделенных знаками операций "*" или "/". Разбивая выражения на термы, а термы на факторы, мы обеспечиваем правильную последовательность выполнения операций.
Например, "2*C5+D6" является выражением, первый терм которого будет "2*C5", а второй терм - "D6". "2*C5" является термом, первый фактор которого будет "2", а второй фактор - "C5"; "D6" состоит из одного фактора - "D6". Фактором могут быть число ("2"), обозначение ячейки ("C5") или выражение в скобках, перед которым может стоять знак минуса.

Рис. 4.10. Блок-схема синтаксического анализа выражений электронной таблицы.
Блок-схема синтаксического анализа выражений электронной таблицы представлена на рис. 4.10. Для каждого грамматического символа (Expression, Term и Factor - выражение, терм и фактор) имеется соответствующая функция-член, которая выполняет его синтаксический анализ и структура которой очень хорошо отражает его грамматику. Построенные таким образом синтаксические анализаторы называются парсерами с рекурсивным спуском (recursive-descent parsers).
Давайте начнем с evalExpression(), то есть с функции, которая выполняет синтаксический разбор выражения:
01 QVariant Cell::evalExpression(const QString &str, int &pos) const
02 {
03 QVariant result = evalTerm(str, pos);
04 while (str[pos] != QChar::Null) {
05 QChar op = str[pos];
06 if (op != '+' && op != '-') return result;
07 ++pos;
08 QVariant term = evalTerm(str, pos);
09 if (result.type() == QVariant::Double
10 && term.type() == QVariant::Double) {
11 if (op == '+') {
12 result = result.toDouble() + term.toDouble();
13 } else {
14 result= result.toDouble() - term.toDouble();
15 }
16 } else {
17 result = Invalid;
18 }
19 }
20 return result;
21 }
Во-первых, мы вызываем функцию evalTerm() для получения значения первого терма. Если за ним идет символ "+" или "-", мы вызываем второй раз evalTerm(); в противном случае выражение состоит из единственного терма, и мы возвращаем его значение в качестве значения всего выражения. После получения значений первых двух термов мы вычисляем результат операции в зависимости от оператора. Если при оценке обоих термов их значения будут иметь тип double, мы рассчитываем результат в виде числа типа double; в противном случае мы устанавливаем результат на значение Invalid.
Мы продолжаем эту процедуру, пока не закончатся термы. Это даст правильный результат, потому что операции сложения и вычитания обладают свойством "ассоциативности слева" (left-associative), то есть "1-2-3" означает "(1-2)-3", а не "1-(2-3)".
01 QVariant Cell::evalTerm(const QString &str, int &pos) const
02 {
03 QVariant result = evalFactor(str, pos);
04 while (str[pos] != QChar::Null) {
05 QChar op = str[pos];
06 if (op != '*' && op != '/')
07 return result;
08 ++pos;
09 QVariant factor = evalFactor(str, pos);
10 if (result.type() == QVariant::Double &&
11 factor.type() == QVariant::Double) {
12 if (op == '*') {
13 result = result.toDouble() * factor.toDouble();
14 } else {
15 if (factor.toDouble() == 0.0) {
16 result = Invalid;
17 } else {
18 result = result.toDouble() / factor.toDouble();
19 }
20 }
21 } else {
22 result = Invalid;
23 }
24 }
25 return result;
26 }
Функция evalTerm() очень напоминает функцию evalExpression(), но, в отличие от последней, она имеет дело с операциями умножения и деления. В функции evalTerm() необходимо учитывать одну тонкость, а именно: нельзя допускать деления на нуль, так как это приводит к ошибке на некоторых процессорах. Хотя не рекомендуется проверять равенство чисел с плавающей точкой из-за ошибки округления, можно спокойно делать проверку на равенство значению 0.0 для предотвращения деления на нуль.
01 QVariant Cell::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 }