Давайте начнем с 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] == '.') {
немного сложнее, чем evalExpression и evalTerm. Мы начинаем с проверки, не является ли фактор отрицательным. Затем мы проверяем наличие открытой скобки. Если она имеется, мы анализируем значение внутри скобок как выражение, вызывая evalExpression. При анализе выражения в скобках evalExpression вызывает функцию evalTerm, которая вызывает функцию evalFactor, которая вновь вызывает функцию evalExpression. Именно в этом месте осуществляется рекурсия при синтаксическом анализе.
Если фактором не является вложенное выражение, мы выделяем следующую лексему (token), и она должна задавать обозначение ячейки или быть числом. Если эта лексема удовлетворяет регулярному выражению в переменной QRegExp, мы считаем, что она является ссылкой на ячейку, и вызываем функцию value для этой ячейки. Ячейка может располагаться в любом месте в электронной таблице, и она может ссылаться на другие ячейки. Такая зависимость не вызывает проблемы и просто приводит к дополнительным вызовам функции value и к дополнительному синтаксическому анализу ячеек с признаком «dirty» («грязный») для перерасчета значений всех зависимых ячеек. Если лексема не является ссылкой на ячейку, мы рассматриваем ее как число.