Россия, Петерубрг, СПБ-ГПУ, 1998 |
Синтаксический справочник
9.3 Размещение
В разделе "2.7" дается неформальное определение правила размещения. В этом разделе это правило определено более точно.
Смысл программы на Haskell может зависеть от размещения ее текста. Влияние размещения на ее смысл может быть полностью описано путем добавления фигурных скобок и точек с запятой в местах, определяемых размещением. Смысл такого дополнения программы состоит в том, чтобы сделать ее не зависимой от размещения текста.
Влияние размещения задано в этом разделе посредством описания того, как добавить фигурные скобки и точки с запятой в текст программы. Спецификация принимает вид функции L, которая выполняет трансляцию. Входом для L являются:
- Поток лексем, заданных лексическим синтаксисом описания Haskell, со следующими дополнительными токенами:
- Если за ключевым словом let, where, do или of не следует лексема {, то токен {n} вставляется после ключевого слова, где n --- отступ следующей лексемы, если она есть, или 0, если был достигнут конец файла.
- Если первая лексема модуля не является { или module, то она предваряется {n}, где n --- отступ лексемы.
- Там, где начало лексемы предваряется только пробельными символами на той же строке, эта лексема предваряется <n>, где n --- отступ лексемы, при условии что она, как следствие первых двух правил, не предваряется {n}. (NB: строковый литерал может простираться на несколько строк (см. раздел
"2.6"
). Поэтому во фрагменте
f = ("Здравствуйте \ \Билл", "Джейк")
<n> не вставляются ни перед \Билл, потому что она не является началом законченной лексемы, ни перед, потому что она не предваряется только пробельными символами.)
- Стек "контекстов размещения", в котором каждый элемент является:
- Или нулем, который означает, что внешний контекст является явным (т.е. программист поставил открывающую фигурную скобку). Если самый внутренний контекст равен 0, то никаких размещающих токенов не будет добавлено, пока не завершится внешний контекст или новый контекст не будет помещен в стек.
- Или целым положительным числом, которое является отступом для внешнего контекста размещения.
"Отступом" лексемы является номер колонки для первого символа этой лексемы; отступом строки является отступ его крайней слева лексемы. Для того чтобы определить номер колонки, предположим, что используется шрифт фиксированной ширины со следующими условностями:
- Символы новая строка, возврат каретки, перевод строки и перевод страницы начинают новую строку.
- Первая колонка обозначается колонка 1, а не 0.
- При табуляции пропускается 8 символов.
- Символ табуляции вызывает вставку достаточного количества пробелов для выравнивания текущей позиции до следующей позиции табуляции.
С целью соблюдения правил размещения, символы Unicode в исходной программе рассматриваются как те же символы фиксированной ширины, что и символы ASCII. Тем не менее, чтобы избежать визуальной путаницы, программистам следует избегать написания программ, в которых смысл неявного размещения зависит от ширины непробельных символов.
Применение L токены [] передает не зависящую от размещения трансляцию токенов, где токены являются результатом лексического анализа модуля и добавления к нему указателей номеров колонок, как описано выше. Определение L заключается в следующем: где мы используем ":" в качестве оператора конструирования потока и "[]" для пустого потока.
L (<n>:ts) (m:ms) | = | ; : (L ts (m:ms)) | если m = n |
= | } : (L (<n>:ts) ms) | если n < m | |
L (<n>:ts) ms | = | L ts ms | |
L ({n}:ts) (m:ms) | = | { : (L ts (n:m:ms)) | если n > m (Замечание 1) |
L ({n}:ts) [] | = | { : (L ts [n]) | если n > 0 (Замечание 1) |
L ({n}:ts) ms | = | { : } : (L (<n>:ts) ms) | (Замечание 2) |
L (}:ts) (0:ms) | = | } : (L ts ms) | (Замечание 3) |
L (}:ts) ms | = | ошибка-разбора | (Замечание 3) |
L ({:ts) ms | = | { : (L ts (0:ms)) | (Замечание 4) |
L (t:ts) (m:ms) | = | } : (L (t:ts) ms) | если m /= 0 и ошибка-разбора (t) |
(Замечание 5) | |||
L (t:ts) ms | = | t : (L ts ms) | |
L [] [] | = | [] | |
L [] (m:ms) | = | } : L [] ms | если m /=0 (Замечание 6) |
Замечание 1. Вложенный контекст должен иметь больший отступ, чем внешний контекст (n>m). Если нет --- L завершается с ошибкой, и компилятору следует указать на ошибку размещения. Пример:
f x = let h y = let p z = z in p in h
Здесь определение p имеет меньший отступ, чем отступ внешнего контекста, который устанавливается в этом случае путем определения h.
Замечание 2. Если первый токен после where (скажем) не имеет отступа, большего чем внешний контекст размещения, то блок должен быть пуст, поэтому вставляются пустые фигурные скобки. Токен {n} заменяется на <n>, чтобы сымитировать ситуацию как если бы пустые фигурные скобки были явными.
Замечание 3. Посредством сопоставления с 0 текущего контекста размещения, мы гарантируем, что явная закрывающая фигурная скобка может быть сопоставлена только явной открывающей фигурной скобке. Если явная закрывающая фигурная скобка будет сопоставлена неявной открывающей фигурной скобке --- возникнет ошибка разбора.
Замечание 4. Это утверждение означает, что все пары фигурных скобок трактуются как явные контексты размещения, включая именованные создание типов данных и их обновление (раздел "3.15" ). В этом заключается разница между этой формулировкой и Haskell 1.4.
Замечание 5. Дополнительное условие ошибка-разбора (t) интерпретируется следующим образом: если токены, порожденные до сих пор L вместе со следующим токеном t представляет недопустимый префикс в грамматике Haskell, а токены, порожденные к этому времени L, за которым следует токен "}", представляют правильный префикс в грамматике Haskell, то ошибка-разбора (t) равна истине.
Проверка m /= 0 контролирует, что неявно добавленная закрывающая фигурная скобка будет сопоставлена неявной открывающей фигурной скобке.
Замечание 6. В конце ввода добавляются все незаконченные закрывающие фигурные скобки. Будет ошибкой оказаться здесь в пределах контекста без размещения (т.е. m = 0).
Если ни одно из данных выше правил не подойдет, то алгоритм завершится неудачей. Он может завершиться неудачей, например, когда будет достигнут конец ввода, и контекст без размещения будет активен, так как закрывающая фигурная скобка пропущена. Некоторые сбойные ситуации не обнаруживаются алгоритмом, хотя они могут быть: например, let }.
Замечание 1 реализует свойство, при котором обработка размещения может быть остановлена преждевременно из-за ошибки разбора. Например,
let x = e; y = x in e'
правильно, потому что оно транслируется в
let { x = e; y = x } in e'
Закрывающая фигурная скобка вставляется вследствие описанного выше правила ошибки разбора. Правило ошибки разбора трудно реализовать в его полной применимости ко всему, потому что выполнение этого влечет применение ассоциативностей. Например, выражение
do a == b == c
имеет единственный однозначный (хотя, возможно, неправильный с точки зрения типов) разбор, а именно:
(do { a == b }) == c
потому что (==) является неассоциативным. Поэтому программистам советуют избегать написания кода, который требует, чтобы синтаксический анализатор вставлял закрывающую фигурную скобку в таких ситуациях.