Главная » Java » StreamTokenizer в Java

0

 

   Задачи лексического анализа потока данных относятся к числу широко известных и традиционных, и в составе пакета Java.io представлен класс StreamTokenizer, позволяющий решать некоторые из них. Поток разбивается на лексемы с помощью объекта StreamTokenizer, конструктор которого принимает в качестве параметра объект типа Reader, выполняющий функцию источника данных; объект StreamTokenizer действует в соответствии с заданными параметрами сканирования данных. На каждой итерации цикла сканирования вызывается метод nextToken, который возвращает очередную считанную из потока лексему и информацию о ее типе, присваивая эти данные полям объект StreamTokenizer.

   Класс StreamTokenizer ориентирован преимущественно на анализ текстов, представляющих   код,   написанный   на   каком-либо   языке   программировани класс нельзя отнести к числу инструментов лексического анализа общего наз чения. Впрочем, многие файлы (в частности, конфигурационные) весьма близ по   структуре   исходным   текстам   программ.   Проектируя   файловые   структуры данных,  вы сможете существенно сэкономить усилия и время,  если выберете стиль  представления   информации,   близкий   к   тому,   которому  отвечает   программный код, и примените для ее анализа объекты класса StreamTokenizer.

  Когда метод nextToken распознает лексему, он возвращает ее тип в виде значения и присваивает последнее полю ttype. Существует четыре типа лексем, воспринимаемых методом nextToken:

 

·         TT_WORD; обнаружено слово; слово сохраняется в поле sval типа String;

 

·         « TT_NUMBER; обнаружено число; число сохраняется в поле nval типа double; распознаются только десятичные числа с плавающей запятой (с десятичной точкой или без таковой) в нормальной нотации (анализатор не распознает ни 3.4е79 как число с плавающей запятой, ни Oxffff как ше-стнадцатеричное число);

 

·         ТТ_ЕО1_; обнаружен признак завершения строки;

 

·         TT_EOF; достигнут конец файла

 

  Подразумевается, что текст, подлежащий анализу, состоит из байтов, значения которых относятся к диапазону от \u0000 до \uOOFF, — корректность распознавания символов Unicode, не принадлежащих указанному интервалу, не гарантируется. Поток ввода включает специальные (special) и обычные (ordinary) символы. К категории специальных относятся те символы, которые анализатор трактует специальным образом, а именно: символы пробела, символы, образующие числа и слова, и т.д. Любой другой символ воспринимается как обычный. Когда очередным символом потока является обычный символ, в качестве его типа возвращается значение этого символа. Если, например, в потоке встречается символ ‘ ?’, не относящийся к специальным, возвращаемым типом этой лексемы (и значением поля ttype) будет i nt-код символа ‘ ?’.

  В качестве примера рассмотрим код метода, выполняющего суммирование числовых значений, которые вводятся из символьного потока:

 

 static double sumStream(Reader in)  throws  IOException  {

      StreamTokenizer nums = new StreamTokenizer(in);

      double  result = 0.0;

       while  (nums.nextToken()   != StreamTokenizer.TT_EOF)   {

               if (nums.ttype == StreamTokenizer.TT_NUMBER)

                   result += nums.nval;

    }

     return  result;

}

 

Вначале на основе потока in типа Reader создается объект StreamTokenizer, a затем на каждом шаге цикла считывается очередная лексема; если лексема представляет число, его значение прибавляется к содержимому переменной r^sult. По достижении конца потока значение result возвращается в кодинициатор.

  Ниже приведен другой пример, связанный с чтением файла, распознаванием лексем-атрибутов вида name=val ue и сохранением последних в форме объектов Коллекции типа Attri butedlmpl, который был реализован нами в разделе 4.4.1.

 

Public  static Attributed  readAttrs(string file)

     throws  IOException

{

FileReader filein = new FileReader(file);

StreamTokenizer in = new StreamTokenizer(fileln);

 Attributedlmpl  attrs = new Attributedlmpl () ;

Attr attr = null;

 

in.commentchar(‘#’);

 in.ordinaryChar(‘/’);

while  (in.nextToken()   != StreamTokenizer.TT_EOF)  {

       if (in.ttype == StreamTokenizer.TT_WORD)   {

            if (attr   != null)   {

                 attr.setvalue(in.sval);

                 attr = null;

               } else {  

attr = new Attr(in.sval);

att rs.add(att r);              

}

} else if (in.ttype = =  ‘=’)  {

      if (attr == null)

         throw new lOException(‘"=’   размещен неверно");

} else  {

    if (attr == null)

   throw new IOException(“Невернoe имя Attr");

    attr.setvalue(new Double(in.nval));

    attr = null;

   }

 }

 return attrs;

}

В файле атрибутов могут использоваться строки комментариев, предваряемые символом ‘ #’. Такие строки при чтении файла игнорируются. Содержимое файлового потока просматривается в поисках строковых лексем, сопровождаемых необязательным символом ‘ = ‘ и словом либо числом. Каждый из таких атрибутов помещается в объект типа Attr, который добавляется в коллекцию атрибутов, представляемую объектом attrs класса Attributedlmpl. По достижении конца потока объект attrs возвращается.

   Задание  символа  комментария   ‘ #’   означает  определение   класса  символов (character   class).   Лексический   анализатор   StreamTokenizer   распознает   несколько классов символов, которые задаются методами, описанными ниже.

 

public void wordchars(int low,   int hi)

Символы, коды которых принадлежат диапазону от low до hi, могут употребляться в составе слов, т.е. лексем типа TT_WORD. Метод может вызываться несколько раз с различными парами аргументов. Словом считается последовательность из одного или нескольких символов, принадлежащих любому из заданных диапазонов.

public void whitespacechars(int low,   int hi)

Символы, коды которых принадлежат диапазону от low до hi, воспринимаются как символы пробела. Символы пробела, как правило, игнорирУ’ ются, за исключением тех, которые разделяют лексемы (скажем, два по следовательных слова). Как и в случае wordChars, метод допускает неоднократный вызов; в такой ситуации набор символов пробела определяется объединением нескольких заданных диапазонов кодов.

public void ordinaryChars(int low,  int hi)

Символы, коды которых принадлежат диапазону от low до hi, трактуются как обычные. Обычный символ возвращается анализатором сам по себе, а не в виде лексемы. Метод позволяет устранить то специальное значение, которым могут обладать символы, определяющие начало комментария, а также разделители, знаки пробелов и символы, употребляемые в составе слов и чисел. В примере метода readAttrs, рассмотренном выше, вызов ordinarychars был использован с той целью, чтобы в процессе анализа символ ‘ /’ расценивался как обычный, а не как признак начала комментария.

public void ordinarychar(int ch)

      Метод аналогичен предыдущему при условии ordinaryChars(ch,   ch).

public void commentchar(int ch)

Символ ch задает начало однострочного комментария — символы после ch и до конца текущей строки воспринимаются как единый символ пробела и игнорируются.

public void quotechar(int ch)

Пары символов ch служат для обозначения констант типа String. При распознавании строковой константы символ ch возвращается в виде лексемы, а в поле sval заносится содержимое константы без ограничивающих символов ch. При чтении строковых констант выполняется обработка некоторых стандартных служебных символов (скажем, внутри строки разрешается использовать признак табуляции \t). Объект StreamTokenizer способен обрабатывать только часть множества служебных символов, которые могут присутствовать в содержимом объектов String Java. В частности, анализатор не реагирует на последовательности вида \uxxxx, \’, \" и (к сожалению) \Q, где Q — символ с кодом ch. Разрешается задавать несколько символов-ограничителей, но константа должна обрамляться только одинаковыми символами. Другими словами, строка, начинающаяся с определенного символа ch, считается завершенной при достижении второго экземпляра того же символа; если в промежутке между ними найден иной ограничивающий символ, он воспринимается как часть содержимого строки.

public void parseNumbers()

Задает, что лексемы, составленные из числовых символов, в процессе анализа должны трактоваться как числа двойной точности с плавающей запятой. При обнаружении числа возвращается тип TT_NUMBER, a полю nval присваивается значение числа. Простых способов отключения этого механизма не существует — следует либо вызвать метод ordinarychars (или ordinaryChar) для всех числовых символов, включая символ десятичной точки (.) и знак минус (-), либо обратиться к методу resetSyntax (см. ниже).

Public void  resetSyntaxO

Очищает внутреннюю синтаксическую таблицу и указывает, что все символы должны трактоваться как обычные. После вызова resetSyntax метод nextToken в процессе чтения данных всегда будет возвращать по одному символу — точно так же, как и inputStream. read.

 

 

Методов, которые позволили бы определять класс символов, соответствующий

заданному символу, или создавать новые классы символов, не существует. Во

как выглядит набор инструкций, задающих классы символов, которые предЛа

гаются по умолчанию для вновь созданного объекта класса StreamTokenizer-

wordCharsC’a1,   ‘z’);     // буквы нижнего регистра ASCII

wordChars(‘A’,   "z”);     // буквы верхнего регистра ASCII

wordchars(128 + 32,  255);    // верхняя часть кодовой таблицы, не ASCII

whitespaceChars(O,   ‘   ‘);     // управляющие коды ASCII

commentChar(‘/’)

quoteChar(‘ “ ’);             

quoteChar(‘\ ‘’);             

parseNumbers();

К обычным символам в этом случае относится большинство знаков пунктуации и арифметических операций, таких как ;, :, [, {, +, = й т.д.

   Изменения, вносимые в структуру классов символов, носят накопительный характер, так что, например, при двукратном вызове wordChars с заданием различных диапазонов символов класс символов типа TT_WORD будет определяться суммой двух указанных диапазонов. Чтобы осуществись замену ранее заданного диапазона новым, следует вначале пометить символы прежнего диапазона как обычные, а затем вызвать соответствующий метод, передав в качестве аргументов значения границ нового диапазона. Обращение к resetSyntax приводит к полной очистке синтаксической таблицы; если необходимо вернуть значения, предусмотренные по умолчанию, надлежит выполнить инструкции, приведенные выше.

   Теперь рассмотрим методы, управляющие основными характеристиками поведения лексического анализатора StreamTokenizer.

 

public void eollssignificant(boolean flag)

Если содержимое flag равно true, признаки завершения строки считаются значимыми, так что nextToken, встретив такой признак, вернет значение ТТ_ЕО1_. В противном случае признаки завершения строки должны трактоваться как символы пробела, поэтому величина TT_EOL никогда не будет возвращена. По умолчанию предусмотрено значение false.

public void siashStarComments(boolean flag)

Если содержимое flag равно true, анализатор распознает комментарии вида /*. . . */. Вызов метода не оказывает влияния на возможность обнаружения комментариев других типов. По умолчанию предусмотрено значение false.

public void siashSlashComments(boolean flag)

Если содержимое flag равно true, анализатор распознает однострочные комментарии вида //. Вызов метода не влияет на возможность обнаружения комментариев других типов. По умолчанию предусмотрено значение false, public void 1 owerCaseMode(boolean flag)

Если  содержимое  flag  равно  true,   все  символы  лексем  вида TT_WOR преобразуются в соответствующие аналоги нижнего регистра (коль скор таковые существуют)  с  помощью  String.toLowerCase.   По  умолчани предусмотрено значение false. Метод не может быть надежно использо ван для сравнения строк Unicode (о проблемах, связанных с преобразо ниями регистров символов Unicode, мы говорили в разделе 11.1.3), поскольку вполне может случиться, что две лексемы равнозначны, но при этом обладают различными представлениями нижнего регистра. Чтобы обеспечить достоверность результата операции сравнения строк, не чувствительной к регистрам символов, следует применять метод String.equalsignoreCase.

 

В составе класса StreamTokenizer существуют и другие методы.

 

public void pushBack()

Возвращает ранее считанную лексему назад в поток. При следующем вызове метод nextToken вновь возвратит эту же лексему. Буфер способен хранить только одну возвращенную лексему, поэтому последовательность вызовов push Вас к равнозначна единственному вызову.

public int lineno()

Возвращает номер текущей строки. Метод находит применение при формировании сообщений об ошибках, обнаруженных в файловых данных.

public String toString()

Возвращает строковое представление последней считанной лексемы, включающее номер строки.

 

 

Источник: Арнолд, Кен, Гослинг, Джеймс, Холмс, Дэвид. Язык программирования Java. 3-е изд .. : Пер. с англ. – М. : Издательский дом «Вильяме», 2001. – 624 с. : ил. – Парал. тит. англ.

По теме:

  • Комментарии