Как определить токен, который является всеми этими символами в наборе A, кроме символов в подмножестве B?

В RFC2616 (HTTP / 1.1) определение "токена" в разделе "2.2 Основные правила" дано как:

token          = 1*<any CHAR except CTLs or separators>

Из этого раздела у меня есть следующие фрагменты, и теперь я хочу определить "TOKEN":

lexer grammar AcceptEncoding;

TOKEN: /* (CHAR excluding (CTRL | SEPARATORS)) */

fragment CHAR: [\u0000-\u007f];
fragment CTRL: [\u0000-\u001f] | \u007f;
fragment SEPARATORS: [()<>@,;:\\;"/\[\]?={|}] | SP | HT;
fragment SP: ' ';
fragment HT: '\t';

Как мне приблизить мой гипотетический оператор "исключения" для определения TOKEN?

3 ответа

В ANTLR нет математики для набора / диапазона. Вы можете комбинировать только несколько наборов / диапазонов с помощью оператора ИЛИ. Типичное правило для ряда непересекающихся диапазонов выглядит так:

fragment LETTER_WHEN_UNQUOTED:
    '0'..'9'
    | 'A'..'Z'
    | '$'
    | '_'
    | '\u0080'..'\uffff'
;

Подстегнутый ответ от @mike-lischke (потому что LETTER_WHEN_UNQUOTED все еще чувствовал себя не так), я охотился за общепринятым подходом к цитируемым строковым литералам в других грамматиках. В собственной грамматике Java 1.6 ANTLR3 Терренса Парра (не правильно text/plain) (через ANTLR3 Grammar List), он достигает "соответствует любому символу, кроме" тильд-оператора ~ в правиле лексера:

STRINGLITERAL
    :   '"' 
        (   EscapeSequence
        |   ~( '\\' | '"' | '\r' | '\n' )        
        )* 
        '"' 
    ;
// Copyright (c) 2007-2008 Terence Parr and possibly Yang Jiang.

ПРИМЕЧАНИЕ: приведенный выше код лицензирован под лицензией BSD, но я не перераспределяю этот фрагмент под лицензией BSD (так как этот пост находится под CC-BY-SA). Вместо этого я использую его в терминах "добросовестного использования", как я их понимаю.

Итак ~ дает мне возможность выразить: "все эти символы в Юникоде, кроме символов в наборе B". "Раздражает, я не могу выбрать набор, который исключен из", подумал я. Но потом понял

TOOHIGH: [\u007f-\uffff];
TOKEN: (~( TOOHIGH | SP | HT | CTRL | SEPARATORS ))+

... все должно быть в порядке. Хотя на практике ANTLR4 не "любит" лексерные под-правила, появляющиеся в "множествах", а обрабатывает только наборы литералов, так что в конечном итоге это становится:

TOKEN:
/* this is given in '2.2 Basic Rules' as:
 *
 *   token          = 1*<any CHAR except CTLs or separators>
 *
 * which I am reducing down to:
 * any character in ASCII 0-127 but _excluding_
 *   CTRL (0-31,127)
 *   SEPARATORS
 *   space (32)
 *   and tab (9)   (which is a CTRL character anyhow)
 */
 ( ~( [\u0000-\u001f] | '\u007f' /*CTRL,HT*/ | [()<>@,;:\\;"/\[\]?={|}] /*SEPARATORS*/ | '\u0020' /*SP*/ | [\u0080-\uffff] /*NON_ASCII*/))*
;

Трюк выражал including the set I do want (Unicode 0-127) с точки зрения excluding the set I don't want (Unicode 128+).

Это гораздо более кратко, чем мой другой ответ. Если это действительно работает, я отмечу это как правильное.

Один из подходов состоит в том, чтобы "сделать математику" для набора символов, чтобы мы могли определить лексические правила, которые когда-либо объединяют символы:

lexer grammar RFC2616;

TOKEN: (DIGIT | UPALPHA | LOALPHA | NON_SEPARATORS)+

/* 
 * split up ASCII 0-127 into 'atoms' of 
 * relevance per '2.2 Basic Rules'.  Regions
 * not requiring to be referenced are not 
 * given a name.
 */

//                [\u0000-\u0008];  /* (control chars) */
fragment HT:      '\u0009';         /* (tab) */
fragment LF:      '\u0010';         /* (LF) */
//                 [\u0011-\u0012];  /* (control chars) */
fragment CR:      '\u0013';         /* (CR)
//                [\u0014-\u001f];  /* (control chars) */
fragment SP:      '\u0020';         /* (space) */
//                [\u0021-\u02f];   /* !"#$%'()*+,-./  */
fragment DIGIT:   [u0030-\u0039];   /* 01234556789  */
//                [\u003a-\u0040];  /* :;<=>@  */
fragment UPALPHA: [\u0041-\u005a];  /* ABCDEFGHIGJLMNOPQRSTUVWXYZ  */
//                [\u005b-\u0060];  /* [\]^_` */
fragment LOALPHA: [\u0061-\u0071];  /* abcdefghijklmnopqrstuvwxyz  */
//                [\u007b-\u007e];  /* {|}~ */
//                '\u007f';         /* (del) */

/* 
 * Considering 'all relevant gaps' and the characters we 
 * cannot use per RFC 2616 Section 2.2 Basic Rules definition 
 * of 'separators', what does that leave us with?  
 * (manually determined)
 */
fragment SEPARATORS: [()<>@,;:\\;"/\[\]?={|}];
fragment NON_SEPARATORS: [!#$%&'*+-.^_`~*];

Я не считаю этот подход особенно удовлетворительным. Другое правило в RFC 2616 хочет быть определено как:

TEXT: <any OCTET except CTLs, but including LWS>
qdtext = <any TEXT except <">>

Это заставит меня продолжить рефакторинг моего подходящего токена "СЕПАРАТОРЫ" выше, например:

fragment QUOT: '"';
fragment SEPARATORS_OTHER_THAN_QUOT: [()<>@,;:\\;/\[\]?={|}];
fragment SEPARATORS: SEPARATORS_OTHER_THAN_QUOT | QUOT;

fragment LWS: SP | HT;

TEXT: DIGIT | UPALPHA | LOALPHA | LWS | SEPARATORS | NON_SEPARATORS;
QDTEXT: DIGIT | UPALPHA | LOALPHA | LWS | SEPARATORS_OTHER_THAN_QUOT | NON_SEPARATORS;

Возможно, это часть работы по написанию лексера, и ее нельзя избежать, но это больше похоже на решение проблемы неправильным путем!

(NB: я не буду отмечать этот ответ как "правильный".)

Другие вопросы по тегам