|
|
|
| |
Главная С++ (часть 3) Функции gettokenQ и skipspacesQ
Функции gettokenQ и skipspacesQ |
Для того чтобы транслятор мог преобразовать экспериментальное ключевое слово в эквивалентный код на языке С++, он должен знать о том, что такое слово обнаружено. Для этого входной файл следует разбить на лексемы. В данном случае термин "лексема" употребляется в самом широком смысле этого слова и означает просто фрагмент текста. Транслятору не нужен тщательный лексический разбор. Он просто должен уметь узнавать идентификаторы (включая ключевые слова), числа и пробелы. Все остальные символы можно обрабатывать как одиночные. Далее приведена функция gettokeno. И Получает следующую лексему из входного потока, bool gettoken(string &tok) { char ch; char ch2; static bool tracklndent = true; tok = nn; ch = fin.getO ; // Ищет EOF и возвращает false, если EOF найден, ifUfin) return false; // Читает пробелы, if(isspace(ch)) { while(isspace(ch)) { tok += ch; // Переустанавливает счетчик отступа (indent counter) // для каждой новой строки. if(ch == '\n') { indent = ""; tracklndent = true; } else if(tracklndent) indent += ch; ch = fin.getO ; } fin.putback(ch) ; return true; } // Перестает отслеживать отступ, обнаружив // первый отличный от пробела символ в строке. tracklndent = false; // Считывает идентификатор или ключевое слово, if(isalpha(ch) || ch=='_') { while(isalpha(ch) || isdigit(ch) || ch=='_') { tok += ch; ch = fin.getO ; } fin.putback(ch) ; return true; } // Считывает число, if(isdigit(ch)) { while(isdigit(ch) || ch=='.' || tolower(ch) == 'e' || ch == '-' || ch ==¦+¦) { tok += ch; ch = fin.getO ; } fin.putback(ch) ; return true; } // Ищет символьную комбинацию \" if(ch == 'WO { ch2 = fin.getO ; if(ch2 == "") { tok += ch; tok += ch2; ch = fin.get(); } else fin.putback(ch2) ; } // Ищет символьную константу '"' if (ch == '\•• ) { ch2 = fin.getO ; if (ch2 == '"') { tok += ch; tok += ch2; return true; } else fin.putback(ch2); } // Ищет начало символов комментария. if(ch == ¦/') { tok += ch; ch = fin.getO; if(ch == '/' || ch == '*') { tok += ch; } else fin.putback(ch); return true; } // Ищет конец символов комментария, if(ch == '*') { tok += ch; ch = fin.getO ; if(ch == '/') { tok += ch; } else fin.putback(ch); return true; } tok += ch; return true; } У функции gettoken о один параметр — строка, именуемая tok и передающая ссылку на объект типа string. Когда функция вернется в вызывающую программу, в этом объекте будет храниться возвращаемая лексема. Функция возвращает true, если лексема прочитана, и false, если достигнут конец файла. Функция gettoken о начинается с присваивания переменной tok пустой строки (null). Затем читается следующий символ из входного файла fin и запоминается в переменной ch. Если во время чтения обнаружен конец файла, функция возвращает false, в противном случае значение ch проверяется на соответствие нескольким вариантам. Во-первых, если переменная ch равна пробелу, начинается цикл, в котором считываются пробелы до первого появления символа, отличного от пробела. Считанные пробелы добавляются к значению переменной tok. Прочитанный непробельный символ возвращается во входной поток с помощью вызова функции fin.putbacko. В конце цикла переменная tok содержит все считанные пробелы, и эта лексема возвращается в вызывающую процедуру-В цикле считывания пробелов надо обратить внимание еще на один вариант обработки. Если начинается новая строка, переменной indent присваивается пустая строка и переменной tracklndent — значение true. Пока nepej менная tracklndent равна true, пробелы запоминаются в переменной indent. После выхода из цикла обработки пробелов переменной tracklndent присваивается значение false. Таким образом, только начальные пробелы в строке сохраняются в переменной indent. Если переменная ch содержит не пробел, поверяются другие возможные варианты. Если очередная лексема — идентификатор или ключевое слово, то она начинается с буквы или знака подчеркивания и читается следующим циклом функции gettokeno. В противном случае, если ch содержит цифру, считывается число. Если же переменная ch — не пробел, не буква, не знак подчеркивания и не цифра, то проверяются 5 специальных вариантов. Первый — последовательность \". Как уже упоминалось, транслятор не обрабатывает текст, заключенный в кавычки. Когда он обнаруживает открывающие кавычки, то просто копирует весь текст между ними и закрывающими кавычками. Однако нужно уметь отличать включенные в текст кавычки (embedded quote) от открывающих и закрывающих. Для этого последовательность \" должна обрабатываться как пара. Подобная ситуация возникает, если символьная константа содержит кавычки. Символы комментариев также должны обрабатываться как символьные пары, поэтому функция gettoken о проверяет //, /* и */. Иногда во время трансляции необходимо избавиться от пробелов, присутствующих в экспериментальном операторе и не имеющих отношения к создаваемому коду на языке С++. Приведенная далее функция skipspaces () делает это. Она просто читает и отбрасывает пробелы, void skipspaces() { char ch; do { ch = fin.getO ; } while(isspace(ch)); f in.putback(ch); } Трансляция цикла foreach Трансляция цикла foreach — наиболее сложная задача. Эту обработку выполняет функция foreacho, приведенная далее. Ч Транслирует цикл foreach. foreach () { string token; string varname; string arrayname; f char forvarname[5] = "_i"; static char counter[2] = "a"; // Создает управляющую переменную цикла // для генерируемого цикла for. s treat(forvarname, counter); counter[0]++; // В файле может быть только 26 циклов foreach, так как число // генерируемых управляющих переменных цикла ограничено диапазоном // от _ia до _iz. При желании это можно изменить, if(counter[0] > 'z') throw SyntaxExc("Too many foreach loops."); fout « "int " « forvarname « " = 0;\n"; // Записывает начало генерируемого цикла for. fout « indent « "for("; skipspaces(); // Считывает "(" • gettoken(token); if(token[0] != '(') throw SyntaxExc("( expected in foreach."); skipspaces(); // Получает тип переменной цикла foreach. gettoken(token); fout « token « " "; skipspaces(); // Читает и сохраняет имя переменной цикла foreach. gettoken (token),varname = token; skipspaces(); // Читает "in" gettoken(token); if(token != "in") throw SyntaxExc("in expected in foreach."); skipspaces(); // Читает имя массива, gettoken(token); arrayname = token; fout « varname « " = " « arrayname « n[0];\n"; // Конструирует результирующую строку. fout « indent + " " « forvarname « " < " « " ( (sizeof " « token « ")/" « "(sizeof " « token « "[0]));\n"; fout « indent + " " « forvarname « "++. " « varname « " = " « arrayname « " [" « forvarname « "])"; skipspaces() ; // Считывает ")" gettoken(token); if(token[0] != ¦) ') throw SyntaxExc(") expected in foreach."); } Транслятор преобразует цикл foreach в эквивалентный ему цикл for. Эта обработка включает в себя решение двух довольно сложных задач. Для их понимания рассмотрим следующий пример: ^uble nums[] = { 1.1, 2.2, 3.3 }; double sum = 0.0; Јoreach(double v in nums) sum += v; Цикл foreach транслируется в приведенный далее код на языке С++. int _ia = 0; for(double v = nums[0]; _ia < ((sizeof nums)/(sizeof nums[0])); _ia++, v = nums[_ia]) sum += v; Прежде всего, обратите внимание на то, что цикл for нуждается в управляющей переменной цикла, в нашем случае названной _ia, но такой переменной нет в цикле foreach. Напоминаю, что переменная, объявленная в цикле foreach, в данном случае v, получает значения элементов массива. Она не является счетчиком цикла. Следовательно, управляющую переменную цикла надо создать. Она не может быть объявлена внутри цикла, так как ее нужно сохранить для объявления v — переменной цикла foreach, локальной в цикле for. Это требует формирования уникального имени переменной типа int, которое не будет конфликтовать с другими переменными, используемыми в их области видимости. Кроме того, в цикле for должны обрабатываться все элементы массива (в данном случае nums), но размер массива не задан в цикле foreach. Это значит, что количество элементов массива нужно вычислить. К счастью, это можно сделать с помощью операции sizeof, которая вскоре будет описана. Теперь давайте рассмотрим каждый шаг процесса трансляции. Преобразование цикла foreach начинается с создания уникальной (будем надеяться) переменной, которая будет использоваться как управляющая переменная цикла for. Имя ее начинается с символьной комбинации _i, к которой добавляется буква из диапазона от а до z. Первая создаваемая в файле управляющая переменная называется _ia, вторая — _ib и т. д. Можно создать до 26 подобных переменных. Это не лучший способ формирования бесконфликтных имен переменных, но достаточно простой и удовлетворяющий потребности эксперимента с циклом foreach. Управляющая переменная получает тип int и ее объявление записывается в выходной файл. Затем с записи "for(" в выходной файл начинается цикл for. Далее конструируется инициализирующая часть цикла. Сначала тип и имя переменной цикла foreach считываются и копируются в выходной файл. Затем ключевое слово in читается и отбрасывается. Потом имя массива читается и используется для инициализации, в результате которой переменной цикла foreach присваивается значение первого элемента массива. Далее формируется часть цикла, содержащая условное выражение. В этой секции управляющая переменная цикла сравнивается со значением, равным количеству элементов массива. Последнее вычисляется с помощью деления р^3' мера массива на размер отдельного его элемента. Это действие необходим0. потому что операция sizeof возвращает размер занимаемой массивом области тгамяти в байтах, а не количество его элементов. В конце создается секция итерации или тело цикла for. В ней значение переменной цикла увеличивается на единицу с помощью операции инкремента и переменной цикла foreach присваивается значение следующего элемента массива. Перед завершением функции foreach о проводится проверка того, что цикл foreach заканчивается символом ")". Последнее замечание: в языке С* цикл foreach может применяться к массивам и коллекциям (которые больше похожи на контейнеры библиотеки STL). Для простоты предлагаемая версия цикла будет обрабатывать только массивы, но вы можете адаптировать ее для обработки в цикле STL-контейнеров.
|
|
|
|
|