Лексическая структура Haskell 98 Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций

2  Лексическая структура

В этой главе мы опишем лексическую структуру нижнего уровня языка Haskell . Большинство деталей может быть пропущено при первом прочтении этого описания.

2.1  Соглашения об обозначениях

Эти соглашения об обозначениях используются для представления синтаксиса:

[pattern] необязательный
{pattern} ноль или более повторений
(pattern) группировка
pat1 | pat2 выбор
pat<pat'> разность --- элементы, порождаемые с помощью pat,
за исключением элементов, порождаемых с помощью pat'
fibonacci терминальный синтаксис в машинописном шрифте

Поскольку синтаксис в этом разделе описывает лексический синтаксис, все пробельные символы выражены явно; нет никаких неявных пробелов между смежными символами. Повсюду используется BNF-подобный синтаксис, чьи правила вывода имеют вид:

nonterm -> alt1 | alt2 | ... | altn

Перевод:
нетерминал -> альтернатива1 | альтернатива2 | ... | альтернативаn

Необходимо внимательно отнестись к отличию синтаксиса металогических символов, например, | и [...], от конкретного синтаксиса терминалов (данных в машинописном шрифте), например, | и [...], хотя обычно это ясно из контекста.

Haskell использует кодировку символов Unicode [11]. Тем не менее, исходные программы в настоящее время написаны в основном в кодировке символов ASCII , используемой в более ранних версиях Haskell .

Этот синтаксис зависит от свойств символов Unicode, определяемых консорциумом Unicode. Ожидается, что компиляторы Haskell будут использовать новые версии Unicode, когда они станут доступными.

2.2  Лексическая структура программы

program -> {lexeme | whitespace }
lexeme -> qvarid | qconid | qvarsym | qconsym
| literal | special | reservedop | reservedid
literal -> integer | float | char | string
special -> ( | ) | , | ; | [ | ] | `| { | }
whitespace -> whitestuff {whitestuff}
whitestuff -> whitechar | comment | ncomment
whitechar -> newline | vertab | space | tab | uniWhite
newline -> return linefeed | return | linefeed | formfeed
return -> возврат каретки
linefeed -> перевод строки
vertab -> вертикальная табуляция
formfeed -> перевод страницы
space -> пробел
tab -> горизонтальная табуляция
uniWhite -> любой пробельный символ Unicode
comment -> dashes [ any<symbol> {any}] newline
dashes -> -- {-}
opencom -> {-
closecom -> -}
ncomment -> opencom ANYseq {ncomment ANYseq}closecom
ANYseq -> {ANY}<{ANY}( opencom | closecom ) {ANY}>
ANY -> graphic | whitechar
any -> graphic | space | tab
graphic -> small | large | symbol | digit | special | : | " | '
small -> ascSmall | uniSmall | _
ascSmall -> a | b | ... | z
uniSmall -> любая буква Unicode нижнего регистра
large -> ascLarge | uniLarge
ascLarge -> A | B | ... | Z
uniLarge -> любая буква Unicode верхнего регистра или заглавная
symbol -> ascSymbol | uniSymbol<special | _ | : | " | '>
ascSymbol -> ! | # | $ | % | & | * | + | . | / | < | = | > | ? | @
| \ | ^ | | | - | ~
uniSymbol -> любой символ или знак пунктуации Unicode
digit -> ascDigit | uniDigit
ascDigit -> 0 | 1 | ... | 9
uniDigit -> любая десятичная цифра Unicode
octit -> 0 | 1 | ... | 7
hexit -> digit | A | ... | F | a | ... | f

Перевод:
программа -> {лексема | пробельная-строка }
лексема -> квалифицированный-идентификатор-переменной
| квалифицированный-идентификатор-конструктора
| квалифицированный-символ-переменной
| квалифицированный-символ-конструктора
| литерал
| специальная-лексема
| зарезервированный-оператор
| зарезервированный-идентификатор
литерал -> целый-литерал
| литерал-с-плавающей-точкой
| символьный-литерал
| строковый-литерал
специальная-лексема -> ( | ) | , | ; | [ | ] | `| { | }
пробельная-строка -> пробельный-элемент {пробельный-элемент}
пробельный-элемент -> пробельный-символ
| комментарий
| вложенный-комментарий
пробельный-символ -> новая-строка
| вертикальная-табуляция
| пробел
| горизонтальная-табуляция
| пробельный-символ-Unicode
новая-строка -> возврат-каретки перевод-строки
| возврат-каретки
| перевод-строки
| перевод-страницы
комментарий -> тире [ любой-символ<символ> {любой-символ}] новая-строка
тире -> -- {-}
начало-комментария -> {-
конец-комментария -> -}
вложенный-комментарий -> начало-комментария ЛЮБАЯ-последовательность {вложенный-комментарий ЛЮБАЯ-последовательность}конец-комментария
ЛЮБАЯ-последовательность -> {ЛЮБОЙ-символ}<{ЛЮБОЙ-символ}( начало-комментария | конец-комментария ) {ЛЮБОЙ-символ}>
ЛЮБОЙ-символ -> графический-символ | пробельный-символ
любой-символ -> графический-символ
| пробел
| горизонтальная-табуляция
графический-символ -> маленькая-буква
| большая-буква
| символ
| цифра
| специальная-лексема
| : | " | '
маленькая-буква -> маленькая-буква-ASCII
| маленькая-буква-Unicode
| _
маленькая-буква-ASCII -> a | b | ... | z
большая-буква -> большая-буква-ASCII | большая-буква-Unicode
большая-буква-ASCII -> A | B | ... | Z
символ -> символ-ASCII
| символ-Unicode<специальная-лексема | _ | : | " | '>
символ-ASCII -> ! | # | $ | % | & | * | + | . | / | < | = | > | ? | @
| \ | ^ | | | - | ~
символ-Unicode -> любой символ или знак пунктуации Unicode
цифра -> цифра-ASCII | цифра-Unicode
цифра-ASCII -> 0 | 1 | ... | 9
цифра-Unicode -> любая десятичная цифра Unicode
восьмиричная-цифра -> 0 | 1 | ... | 7
шестнадцатиричная-цифра -> цифра | A | ... | F | a | ... | f

Лексический анализ должен использовать правило "максимального потребления": в каждой точке считывается наиболее длинная из возможных лексем, которая удовлетворяет правилу вывода lexeme (лексемы). Таким образом, несмотря на то, что case является зарезервированным словом, cases таковым не является. Аналогично, несмотря на то, что = зарезервировано, == и ~= --- нет.

Любой вид whitespace (пробельной-строки) также является правильным разделителем для лексем.

Символы, которые не входят в категорию ANY (ЛЮБОЙ-символ), недопустимы в программах на Haskell и должны приводить к лексической ошибке.

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

Комментарии являются правильными пробельными символами.

Обычный комментарий начинается с последовательности двух или более следующих друг за другом символов тире (например, --) и простирается до следующего символа новой строки. Последовательность тире не должна являться частью правильной лексемы. Например, "-->" или "|--" не являются началом комментария, потому что оба они являются правильными лексемами; но "--foo" начинает комментарий.

Вложенный комментарий начинается с "{-" и заканчивается "-}". Нет никаких правильных лексем, которые начинаются с "{-"; следовательно, например, "{---" начинает вложенный комментарий несмотря на замыкающие символы тире.

Сам комментарий не подвергается лексическому анализу. Вместо этого, первое, не имеющее соответствующей пары вхождение строки "-}" заканчивает вложенный комментарий. Вложенные комментарии могут быть вложенными на любую глубину: любое вхождение строки "{-" в пределах вложенного комментария начинает новый вложенный комментарий, заканчивающийся "-}". В пределах вложенного комментария каждый "{-" сопоставляется с соответствующим вхождением "-}".

В обычном комментарии последовательности символов "{-" и "-}" не имеют никакого специального значения, и во вложенном комментарии последовательность символов тире не имеет никакого специального значения.

Вложенные комментарии также используются для указаний компилятору, объясненных в главе 11.

Если некоторый код закомментирован с использованием вложенного комментария, то любое вхождение {- или -} в пределах строки или в пределах комментария до конца строки в этом коде будет влиять на вложенные комментарии.

2.4  Идентификаторы и операторы

varid -> (small {small | large | digit | ' })<reservedid>
conid -> large {small | large | digit | ' }
reservedid -> case | class | data | default | deriving | do | else
| if | import | in | infix | infixl | infixr | instance
| let | module | newtype | of | then | type | where | _

Перевод:
идентификатор-переменной -> (маленькая-буква {маленькая-буква | большая-буква | цифра | ' })<зарезервированный-идентификатор>
идентификатор-конструктора -> большая-буква {маленькая-буква | большая-буква | цифра | ' }
зарезервированный-идентификатор -> case | class | data | default | deriving | do | else
| if | import | in | infix | infixl | infixr | instance
| let | module | newtype | of | then | type | where | _

Идентификатор состоит из буквы, за которой следует ноль или более букв, цифр, символов подчеркивания и одинарных кавычек. Идентификаторы лексически делятся на два пространства имен (раздел 1.4): те, которые начинаются со строчной буквы (идентификаторы переменных), и те, которые начинаются с заглавной буквы (идентификаторы конструкторов). Идентификаторы зависят от регистра букв: name, naMe и Name --- это три различных идентификатора (первые два являются идентификаторами переменных, последний --- идентификатором конструктора).

Символ подчеркивания "_" рассматривается как строчная буква, и может появляться везде, где может появляться строчная буква. Тем не менее, "_" сам по себе является зарезервированным идентификатором, который используется для обозначения группы любых символов в образцах. Компиляторы, которые выводят предупреждения о неиспользованных идентификаторах, подавляют вывод таких предупреждений для идентификаторов, начинающихся с символа подчеркивания. Это позволяет программистам использовать "_foo" для параметра, который, как они ожидают, не будет использоваться.

varsym -> ( symbol {symbol | :})<reservedop | dashes>
consym -> (: {symbol | :})<reservedop>
reservedop -> .. | : | :: | = | \ | | | <- | -> | @ | ~ | =>

Перевод:
символ-переменной -> ( символ {символ | :})<зарезервированный-оператор | тире>
символ-конструктора -> (: {символ | :})<зарезервированный-оператор>
зарезервированный-оператор -> .. | : | :: | = | \ | | | <- | -> | @ | ~ | =>

Символы операторов образуются из одного или более символов, в соответствии с приведенным выше определением, и лексически делятся на два пространства имен (раздел 1.4):

Заметьте, что само по себе двоеточие ":" зарезервировано исключительно для использования в качестве конструктора списков в Haskell; это делает их интерпретацию единообразной с другими частями синтаксиса списка, как например "[]" и "[a,b]".

За исключением специального синтаксиса для префиксного отрицания, все остальные операторы являются инфиксными, хотя каждый инфиксный оператор можно использовать в сечении для выполнения частичного применения операторов (см. раздел 3.5). Все стандартные инфиксные операторы являются просто предопределенными символами и могут использоваться в связывании имен.

В оставшейся части описания будут использоваться шесть различных видов имен:

varid (переменные)
conid (конструкторы)
tyvar -> varid (переменные типов)
tycon -> conid (конструкторы типов)
tycls -> conid (классы типов)
modid -> conid (модули)

Перевод:
идентификатор-переменной (переменные)
идентификатор-конструктора (конструкторы)
переменная-типа -> идентификатор-переменной (переменные типов)
конструктор-типа -> идентификатор-конструктора (конструкторы типов)
класс-типа -> идентификатор-конструктора (классы типов)
идентификатор-модуля -> идентификатор-конструктора (модули)

Переменные и переменные типов представляются идентификаторами, начинающимися с маленьких букв, а остальные четыре --- идентификаторами, начинающимися с больших букв; также, переменные и конструкторы имеют инфиксные формы, остальные четыре --- нет. Пространства имен также рассматриваются в разделе 1.4.

В определенных обстоятельствах имя может быть снабжено необязательным квалификатором, т.е. быть квалифицировано, посредством присоединения к нему слева идентификатора модуля. Это относится к именам переменных, конструкторов, конструкторов типов и классов типов, но не относится к переменным типов или именам модулей. Квалифицированные имена подробно рассматриваются в главе 5.

qvarid -> [modid .] varid
qconid -> [modid .] conid
qtycon -> [modid .] tycon
qtycls -> [modid .] tycls
qvarsym -> [modid .] varsym
qconsym -> [modid .] consym

Перевод:
квалифицированный-идентификатор-переменной -> [идентификатор-модуля .] идентификатор-переменной
квалифицированный-идентификатор-конструктора -> [идентификатор-модуля .] идентификатор-конструктора
квалифицированный-конструктор-типа -> [идентификатор-модуля .] конструктор-типа
квалифицированный-класс-типа -> [идентификатор-модуля .] класс-типа
квалифицированный-символ-переменной -> [идентификатор-модуля .] символ-переменной
квалифицированный-символ-конструктора -> [идентификатор-модуля .] символ-конструктора
Так как квалифицированное имя является лексемой, пробелы недопустимы между квалификатором и именем. Примеры лексического анализа приведены ниже.

Это интерпретируется как
f.g f . g (три токена)
F.g F.g (квалифицированное `g')
f.. f .. (два токена)
F.. F.. (квалифицированный `.')
F. F . (два токена)

Квалификатор не изменяет синтаксическую интерпретацию имени; например, Prelude.+ --- это инфиксный оператор той же ассоциативности и того же приоритета, что и определение + в Prelude (раздел 4.4.2).

2.5  Числовые литералы

decimal -> digit{digit}
octal -> octit{octit}
hexadecimal -> hexit{hexit}
integer -> decimal
| 0o octal | 0O octal
| 0x hexadecimal | 0X hexadecimal
float -> decimal . decimal [exponent]
| decimal exponent
exponent -> (e | E) [+ | -] decimal

Перевод:
десятичный-литерал -> цифра{цифра}
восьмиричный-литерал -> восьмиричная-цифра{восьмиричная-цифра}
шестнадцатиричный-литерал -> шестнадцатиричная-цифра{шестнадцатиричная-цифра}
целый-литерал -> десятичный-литерал
| 0o восьмиричный-литерал | 0O восьмиричный-литерал
| 0x шестнадцатиричный-литерал | 0X шестнадцатиричный-литерал
литерал-с-плавающей-точкой -> десятичный-литерал . десятичный-литерал [экспонента]
| десятичный-литерал экспонента
экспонента -> (e | E) [+ | -] десятичный-литерал
Есть два различных вида числовых литералов: целые и с плавающей точкой. Целые литералы можно задавать в десятичной (по умолчанию), восьмиричной (начинается с 0o или 0O) или шестнадцатиричной записи (начинается с 0x или 0X). Литералы с плавающей точкой всегда являются десятичными. Литерал с плавающей точкой должен содержать цифры и перед, и после десятичной точки; это гарантирует, что десятичная точка не будет ошибочно принята за другое использование символа точка. Отрицательные числовые литералы рассматриваются в разделе 3.4. Типизация числовых литералов рассматривается в разделе 6.4.1.

2.6  Символьные и строковые литералы

char -> ' (graphic<' | \> | space | escape<\&>) '
string -> " {graphic<" | \> | space | escape | gap}"
escape -> \ ( charesc | ascii | decimal | o octal | x hexadecimal )
charesc -> a | b | f | n | r | t | v | \ | " | ' | &
ascii -> ^cntrl | NUL | SOH | STX | ETX | EOT | ENQ | ACK
| BEL | BS | HT | LF | VT | FF | CR | SO | SI | DLE
| DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN
| EM | SUB | ESC | FS | GS | RS | US | SP | DEL
cntrl -> ascLarge | @ | [ | \ | ] | ^ | _
gap -> \ whitechar {whitechar}\

Перевод:
символьный-литерал -> ' (графический-символ<' | \> | пробел | эскейп-символ<\&>) '
строковый-литерал -> " {графический-символ<" | \> | пробел | эскейп-символ | разрыв}"
эскейп-символ -> \ ( символ-эскейп | символ-ascii | десятичный-литерал | o восьмиричный-литерал | x шестнадцатиричный-литерал )
символ-эскейп -> a | b | f | n | r | t | v | \ | " | ' | &
символ-ascii -> ^управляющий-символ | NUL | SOH | STX | ETX | EOT | ENQ | ACK
| BEL | BS | HT | LF | VT | FF | CR | SO | SI | DLE
| DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN
| EM | SUB | ESC | FS | GS | RS | US | SP | DEL
управляющий-символ -> большая-буква-ASCII | @ | [ | \ | ] | ^ | _
разрыв -> \ пробельный-символ {пробельный-символ}\

Символьные литералы записываются между одинарными кавычками, как например 'a', а строки --- между двойными кавычками, как например "Hello".

Эскейп-коды можно использовать в символах и строках для представления специальных символов. Обратите внимание, что одинарную кавычку ' можно использовать внутри строки как есть, но для того, чтобы использовать ее внутри символа, необходимо записать перед ней символ обратной косой черты (\). Аналогично, двойную кавычку " можно использовать внутри символа как есть, но внутри строки она должна предваряться символом обратной косой черты. \ всегда должен предваряться символом обратной косой черты. Категория charesc (символ-эскейп) также включает переносимые представления для символов "тревога" (\a), "забой" (\b), "перевод страницы" (\f), "новая строка" (\n), "возврат каретки" (\r), "горизонтальная табуляция" (\t) и "вертикальная табуляция" (\v).

Эскейп-символы для кодировки Unicode, включая символы управления, такие как \^X, также предусмотрены. Числовые эскейп-последовательности, такие как \137, используются для обозначения символа с десятичным представлением 137; восьмиричные (например, \o137) и шестнадцатиричные (например, \x37) представления также допустимы.

Согласующиеся с правилом "максимального потребления", числовые эскейп-символы в строках состоят изо всех последовательных цифр и могут иметь произвольную длину. Аналогично, единственный неоднозначный эскейп-код ASCII "\SOH" при разборе интерпретируется как строка длины 1. Эскейп-символ \& предусмотрен как "пустой символ", чтобы позволить создание таких строк, как "\137\&9" и "\SO\&H" (обе длины два). Поэтому "\&" эквивалентен "", а символ '\&' недопустим. Дальнейшие эквивалентности символов определены в разделе 6.1.2.

Строка может включать "разрыв" --- две обратные косые черты, окруженные пробельными символами, которые игнорируются. Это позволяет записывать длинные программные строки на более чем одной строке файла, для этого надо добавлять обратную косую черту (бэкслэш) в конец каждой строки файла и в начале следующей. Например,

"Это бэкслэш \\, так же как \137 --- \
    \ числовой эскейп-символ и \^X --- управляющий символ."  

Строковые литералы в действительности являются краткой записью для списков символов (см. раздел 3.7).

2.7  Размещение

В Haskell разрешено опускать фигурные скобки и точки с запятой, используемые в нескольких правилах вывода грамматики, посредством использования определенного размещения текста программы с тем, чтобы отразить ту же самую информацию. Это позволяет использовать и зависящий от размещения текста, и не зависящий от размещения текста стили написания кода, которые можно свободно смешивать в одной программе. Поскольку не требуется располагать текст определенным образом, программы на Haskell можно непосредственно генерировать другими программами.

Влияние размещения текста программы на смысл программы на Haskell можно полностью установить путем добавления фигурных скобок и точек с запятой в местах, определяемых размещением. Смысл такого дополнения программы состоит в том, чтобы сделать ее не зависимой от размещения текста.

Будучи неофициально заявленными, фигурные скобки и точки с запятой добавляются следующим образом. Правило размещения текста (или правило "вне игры") вступает в силу всякий раз, когда открытая фигурная скобка пропущена после ключевого слова where, let, do или of. Когда это случается, отступ следующей лексемы (неважно на новой строке или нет) запоминается, и вставляется пропущенная открытая фигурная скобка (пробельные символы, предшествующие лексеме, могут включать комментарии). Для каждой последующей строки выполняется следующее: если она содержит только пробельные символы или больший отступ, чем предыдущий элемент, то это означает, что продолжается предыдущий элемент (ничего не добавляется); если строка имеет тот же отступ, это означает, что начинается новый элемент (вставляется точка с запятой); если строка имеет меньший отступ, то это означает, что эакончился список размещения (вставляется закрывающая фигурная скобка). Если отступ лексемы без фигурных скобок, которая следует непосредственно за where, let, do или of, меньше чем или равен текущему уровню углубления, то вместо начала размещения вставляется пустой список "{}" и обработка размещения выполняется для текущего уровня (т.е. вставляется точка с запятой или закрывающая фигурная скобка). Закрывающая фигурная скобка также вставляется всякий раз, когда заканчивается синтаксическая категория, содержащая список размещения; то есть если неправильная лексема встретится в месте, где была бы правильной закрывающая фигурная скобка, вставляется закрывающая фигурная скобка. Правило размещения добавляет закрывающие фигурные скобки, которые соответствуют только тем открытым фигурным скобкам, которые были добавлены согласно этому правилу; явная открытая фигурная скобка должна соответствовать явной закрывающей фигурной скобке. В пределах этих явных открытых фигурных скобок, никакая обработка размещения не выполняется для конструкций вне фигурных скобок, даже если строка выравнена левее (имеет меньший отступ) более ранней неявной открытой фигурной скобки.

В разделе 9.3 дано более точное определение правил размещения.

Согласно этим правилам, отдельный символ новой строки на самом деле может завершить несколько списков размещения. Также эти правила разрешают:

f x = let a = 1; b = 2 
          g y = exp2
       in exp1

делая a, b и g частью одного и того же списка размещения.

В качестве примера на риc. 2.1 изображен (несколько запутанный) модуль, а на риc. 2.2 показан результат применения к нему правила размещения. Обратите внимание, в частности, на: (a) строку, начинающую }};pop, в которой завершение предыдущей строки вызывает три применения правила размещения, соответствующих глубине (3) вложенной инструкции where, (b) закрывающие фигурные скобки в инструкции where, вложенной в пределах кортежа и case-выражения, вставленные потому, что был обнаружен конец кортежа, и (c) закрывающую фигурную скобку в самом конце, вставленную из-за нулевого отступа лексемы конца файла.


module AStack( Stack, push, pop, top, size ) where
data Stack a = Empty 
             | MkStack a (Stack a)

push :: a -> Stack a -> Stack a
push x s = MkStack x s

size :: Stack a -> Int
size s = length (stkToLst s)  where
           stkToLst  Empty         = []
           stkToLst (MkStack x s)  = x:xs where xs = stkToLst s

pop :: Stack a -> (a, Stack a)
pop (MkStack x s)
  = (x, case s of r -> i r where i x = x) -- (pop Empty) является ошибкой

top :: Stack a -> a
top (MkStack x s) = x                     -- (top Empty) является ошибкой

Рис. 1

Пример программы


module AStack( Stack, push, pop, top, size ) where
{data Stack a = Empty 
             | MkStack a (Stack a)

;push :: a -> Stack a -> Stack a
;push x s = MkStack x s

;size :: Stack a -> Int
;size s = length (stkToLst s)  where
           {stkToLst  Empty         = []
           ;stkToLst (MkStack x s)  = x:xs where {xs = stkToLst s

}};pop :: Stack a -> (a, Stack a)
;pop (MkStack x s)
  = (x, case s of {r -> i r where {i x = x}}) -- (pop Empty) является ошибкой

;top :: Stack a -> a
;top (MkStack x s) = x                        -- (top Empty) является ошибкой
}

Рис. 2

Пример программы, дополненной размещением


Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций
Декабрь 2002