Действительно ли грамматика D не зависит от контекста? - PullRequest
59 голосов
/ 08 августа 2011

Я опубликовал это в группе новостей D несколько месяцев назад, но по какой-то причине ответ так и не убедил меня, поэтому я решил спросить его здесь.


Грамматика D, по-видимому, не зависит от контекста .

Однако грамматика C ++ (даже без макросов) отсутствует. ( Пожалуйста, прочитайте это внимательно!)

Теперь предоставлено, Я ничего не знаю (официально) о компиляторах, лексерах и парсерах.Все, что я знаю, это то, что я узнал в Интернете.
А вот что (я считаю) я понял относительно контекста в не столь техническом жаргоне:

Грамматика языка не зависит от контекста в том и только в том случае, если вы всегда можете понять значение (хотя и не обязательно точное поведение) данного фрагмента кода без необходимости «искать» где-либо еще.

Или даже меньше строгость:

Грамматика не может быть контекстно-зависимой, если мне нужно, я не могу сказать типвыражение, просто взглянув на него.

Так, например, C ++ не проходит тест без контекста, поскольку значение из confusing<sizeof(x)>::q < 3 > (2) зависит от значение из q.

Пока все хорошо.

Теперь мой вопрос: можно ли сказать то же самое о D?

В D хеш-таблицы могут быть созданы с помощью объявления Value[Key], например

int[string] peoplesAges;   // Maps names to ages

Статические массивы могут быть определены в похожем синтаксисе:

int[3] ages;   // Array of 3 elements

И шаблоны могут быть использованы, чтобы сбить их с толку:

template Test1(T...)
{
    alias int[T[0]] Test;
}

template Test2(U...)
{
    alias int[U] Test2;  // LGTM
}

Test1!(5) foo;
Test1!(int) bar;
Test2!(int) baz;  // Guess what? It's invalid code.

Это означает, что я не могу сказать значение T[0] или U, просто взглянув на него (т. е. это может быть число, это может быть тип данных или кортеж "Бог знает что").Я даже не могу сказать, является ли выражение грамматически верным (поскольку int[U] определенно не так - у вас не может быть хеш-таблицы с кортежами в качестве ключей или значений).

Любое дерево синтаксического анализа, которое я пытаюсь выполнитьчтобы Test не смог бы иметь какой-либо смысл (поскольку ему нужно было бы знать, содержит ли узел тип данных по сравнению с литералом или идентификатором), если он не задерживает результатдо тех пор, пока значение T не станет известно (что делает его зависимым от контекста).

Учитывая это, D фактически не зависит от контекста или я неправильно понимаю концепцию?

Почему/ почему нет?


Обновление:

Я просто подумал, что прокомментирую: это действительно интересно увидеть ответы, так как:

  • В некоторых ответах утверждается, что C ++ и D не могут быть контекстно-свободными
  • В некоторых ответах утверждается, что C ++ и D оба не зависят от контекста
  • Некоторые ответы подтверждают утверждение, что C ++ является контекстно-зависимым, в то время как D не
  • Никто еще не утверждал, чтоt C ++ не зависит от контекста, в то время как D зависит от контекста: -)

Я не могу сказать, учусь ли я или запутался, но в любом случае я радспросил это ... спасибо, что нашли время ответить всем!

Ответы [ 7 ]

42 голосов
/ 08 августа 2011

Быть свободным от контекста - это прежде всего свойство порождающих грамматик. Это означает, что то, что может генерировать нетерминал, не будет зависеть от контекста, в котором появляется нетерминал (в неконтекстной генеративной грамматике само понятие «строка, сгенерированная заданным нетерминалом», как правило, трудно определить). Это не мешает генерировать одну и ту же строку символов двумя нетерминалами (поэтому одинаковые строки символов появляются в двух разных контекстах с различным значением) и не имеет ничего общего с проверкой типов.

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

На практике ни один язык программирования не является контекстно-свободным, потому что такие вещи, как «переменная должна быть объявлена ​​до ее использования», не могут быть проверены с помощью контекстно-свободной грамматики (они могут быть проверены некоторыми другими видами грамматик) , Это неплохо, на практике проверяемые правила делятся на две части: те, которые вы хотите проверить с помощью грамматики, и те, которые вы проверяете в семантическом проходе (и это разделение также позволяет лучше сообщать об ошибках и восстанавливать их, так что вы иногда хотите принять больше в грамматике, чем это было бы возможно для улучшения диагностики ваших пользователей).

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

Я не знаю достаточно о D, чтобы знать, есть ли удобный контекст языковых правил в неконтекстной грамматике с семантическими проверками, но то, что вы показываете, далеко не доказывает, что это не так.

21 голосов
/ 08 августа 2011

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

По сути, это означает, что определение элемента языка не может изменяться в зависимости от того, какие элементы его окружают. Под элементами языка я имею в виду такие понятия, как выражения и идентификаторы , а не конкретные экземпляры этих понятий внутри программ, такие как a + b или count.

Давайте попробуем построить конкретный пример. Рассмотрим это простое утверждение на языке COBOL:

   01 my-field PICTURE 9.9 VALUE 9.9.

Здесь я определяю поле, то есть переменную, размер которой должен содержать одну целую цифру, десятичную точку и одну десятичную цифру с начальным значением 9,9. Очень неполная грамматика для этого может быть:

field-declaration ::= level-number identifier 'PICTURE' expression 'VALUE' expression '.'
expression ::= digit+ ( '.' digit+ )

К сожалению, допустимые выражения, которые могут следовать за PICTURE, не являются теми же действительными выражениями, которые могут следовать за VALUE. Я мог бы переписать второе производство в моей грамматике следующим образом:

'PICTURE' expression ::= digit+ ( '.' digit+ ) | 'A'+ | 'X'+
'VALUE' expression ::= digit+ ( '.' digit+ )

Это сделает мою грамматику контекстно-зависимой, потому что expression будет отличаться в зависимости от того, была ли она найдена после 'PICTURE' или после 'VALUE'. Однако, как уже было отмечено, это ничего не говорит о базовом языке. Лучшая альтернатива будет:

field-declaration ::= level-number identifier 'PICTURE' format 'VALUE' expression '.'
format ::= digit+ ( '.' digit+ ) | 'A'+ | 'X'+
expression ::= digit+ ( '.' digit+ )

, который не зависит от контекста.

Как видите, это сильно отличается от вашего понимания. Рассмотрим:

a = b + c;

Очень мало вы можете сказать об этом утверждении, не просматривая объявления a, b и c, ни на одном из языков, для которых это допустимое утверждение, однако само по себе это не означает, что какой-либо из этих языки не являются контекстно-свободными. Возможно, вас смущает тот факт, что свобода контекста отличается от двусмысленности. Это упрощенная версия вашего примера C ++:

a < b > (c)

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

identifier assignment identifier binary-operator identifier semi-colon

В некоторых случаях вы можете устранить неоднозначности, введя контекстную чувствительность на уровне грамматики. Я не думаю, что это имеет место с неоднозначным примером выше: в этом случае вы не можете устранить неоднозначность, не зная, является ли шаблон шаблоном или нет. Обратите внимание, что когда такая информация недоступна, например, когда она зависит от конкретной специализации шаблона, язык предоставляет способы устранения неоднозначностей: именно поэтому вам иногда приходится использовать typename для ссылки на определенные типы в шаблонах или для использования template при вызове шаблонов функций-членов.

9 голосов
/ 01 ноября 2011

Уже есть много хороших ответов, но поскольку вы не осведомлены о грамматиках, парсерах, компиляторах и т. Д., Позвольте мне продемонстрировать это на примере.

Во-первых, концепция грамматики довольно интуитивна.Представьте себе набор правил:

S -> a T
T -> b G t
T -> Y d
b G -> a Y b
Y -> c
Y -> lambda (nothing)

И представьте, что вы начинаете с S.Заглавные буквы не являются терминалами, а строчные - терминалами.Это означает, что если вы получаете предложение всех терминалов, вы можете произнести грамматику, сгенерировавшую это предложение, как «слово» в языке.Представьте себе такие замены с помощью приведенной выше грамматики (заменяется фраза между * фразу *):

*S* -> a *T* -> a *b G* t -> a a *Y* b t -> a a b t

Итак, я мог бы создать aabt с этой грамматикой.

Хорошо,вернуться к основной строке.

Давайте предположим простой язык.У вас есть числа, два типа (int и string) и переменные.Вы можете выполнять умножение на целые числа и сложение на строках, но не наоборот.

Первое, что вам нужно, это лексер.Это обычно обычная грамматика (или равная ей, DFA, или в равной степени регулярное выражение), которая соответствует токенам программы.Распространено выражать их в регулярных выражениях.В нашем примере:

(я создаю эти синтаксисы)

number: [1-9][0-9]*    // One digit from 1 to 9, followed by any number
                       // of digits from 0-9
variable: [a-zA-Z_][a-zA-Z_0-9]*  // You get the idea. First a-z or A-Z or _
                                  // then as many a-z or A-Z or _ or 0-9
                                  // this is similar to C
int: 'i' 'n' 't'
string: 's' 't' 'r' 'i' 'n' 'g'
equal: '='
plus: '+'
multiply: '*'

whitespace: (' ' or '\n' or '\t' or '\r')*   // to ignore this type of token

Итак, теперь у вас есть обычная грамматика, токенизирующая ваш ввод, но она ничего не понимает вструктура.

Тогда вам нужен парсер.Парсер, это обычно грамматика без контекста.Грамматика, не зависящая от контекста, означает, что в грамматике у вас есть только единственные нетерминалы в левой части правил грамматики.В примере в начале этого ответа правило

b G -> a Y b

делает контекст грамматики- чувствительным , потому что слева у вас есть b G, а не просто G.Что это значит?

Хорошо, когда вы пишете грамматику, каждый из нетерминалов имеет значение.Давайте напишем контекстную грамматику для нашего примера (| означает или. Как будто вы пишете много правил в одной строке):

program -> statement program | lambda
statement -> declaration | executable
declaration -> int variable | string variable
executable -> variable equal expression
expression -> integer_type | string_type
integer_type -> variable multiply variable |
                variable multiply number |
                number multiply variable |
                number multiply number
string_type -> variable plus variable

Теперь эта грамматика может принимать этот код:

x = 1*y
int x
string y
z = x+y

Грамматически, этот код правильный.Итак, давайте вернемся к тому, что означает отсутствие контекста.Как вы можете видеть в приведенном выше примере, когда вы раскрываете executable, вы генерируете одну инструкцию вида variable = operand operator operand, не обращая внимания на то, в какой части кода вы находитесь.В самом начале или в середине, определены ли переменные или нет, или совпадают ли типы, вы не знаете и вам все равно.

Далее вам нужна семантика.Это где контекстно-зависимые грамматики вступают в игру.Во-первых, позвольте мне сказать вам, что на самом деле никто на самом деле не пишет контекстно-зависимую грамматику (потому что ее синтаксический анализ слишком сложен), а только кусочки кода, которые парсер вызывает при разборе ввода (это называется подпрограммой действий. Хотя это не так).единственный способ).Формально, однако, вы можете определить все, что вам нужно.Например, чтобы убедиться, что вы определили переменную перед ее использованием, вместо этого

executable -> variable equal expression

вам нужно иметь что-то вроде:

declaration some_code executable -> declaration some_code variable equal expression

более сложным, чтобы убедиться, чтоvariable в объявлении соответствует вычисляемому.

В любом случае, я просто хотел дать вам идею.Итак, все эти вещи являются контекстно-зависимыми:

  • Проверка типа
  • Количество аргументов для функции
  • значение по умолчанию для функции
  • if member существует в obj в коде: obj.member
  • Почти все, на что это не похоже: отсутствует ; или }

Надеюсь, у вас есть представление о том, чторазличия (если вы этого не сделаете, я был бы более чем рад объяснить).

Итак, в заключение:

  • Lexer использует обычную грамматику для токенизации ввода
  • Parser использует контекстно-свободную грамматику, чтобы убедиться, что программа имеет правильную структуру
  • Семантический анализатор использует контекстно-зависимую грамматику для проверки типов, сопоставления параметров и т. Д. И т. Д.

Хотя это не всегда так.Это просто показывает вам, как каждый уровень должен стать более мощным, чтобы иметь возможность делать больше вещей.Тем не менее, каждый из упомянутых уровней компилятора на самом деле мог бы быть более мощным.

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

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

Чтобы ответить на ваш вопрос!На примере, который вы упомянули, становится ясно, что грамматика c ++ не является контекстно-свободной.Язык D, я абсолютно не знаю, но вы должны быть в состоянии рассуждать об этом сейчас.Думайте об этом так: в контекстно-свободной грамматике нетерминал может расширяться, не принимая во внимание что-либо, НО структуру языка.Подобно тому, что вы сказали, он расширяется, нигде не «глядя».

Знакомый пример - это естественные языки.Например, на английском вы говорите:

sentence -> subject verb object clause
clause -> .... | lambda

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

I go there because I want to

или

I jump you that I is air

Как видите, второе имеет правильную структуру, но не имеет смысла.Что касается грамматики без контекста, значение не имеет значения.Он просто расширяет verb до любого глагола, не «глядя» на остальную часть предложения.

Итак, если вы думаете, что D должен в какой-то момент проверить, как что-то определено в другом месте, просто сказать, что программа структурно правильно, тогда его грамматика не является контекстно-свободной.Если вы изолируете какую-либо часть кода и он все еще может сказать, что он структурно корректен, то он не зависит от контекста.

8 голосов
/ 08 августа 2011

Грамматика не может быть контекстно-зависимой, если мне нужно, я не могу определить тип выражения, просто взглянув на него.

Нет, это неправильно.Грамматика не может быть контекстно-свободной, если вы не можете определить, является ли она выражением, просто взглянув на нее и на текущее состояние анализатора (я нахожусь в функции, в пространстве имен и т. Д.).

Тип выражения, однако, является семантическим, а не синтаксическим, и синтаксический анализатор и грамматика не дают копейки о типах или семантической валидности, а также о том, можно ли использовать кортежи в качестве значений или ключей.в хэш-картах, или если вы определили этот идентификатор перед его использованием.

Грамматика не заботится о том, что это значит, или имеет ли это смысл.Его волнует только то, что это .

7 голосов
/ 16 августа 2011

В лексере D есть конструкция:

string ::= q" Delim1 Chars newline Delim2 "

где Delim1 и Delim2 являются совпадающими идентификаторами, а Chars не содержит символа новой строки Delim2.

Эта конструкция является контекстно-зависимой, поэтому грамматика лексера D является контекстно-зависимой.

Прошло несколько лет с тех пор, как я много работал с грамматикой D, поэтому я не могу вспомнить все проблемные точки на макушке, или даже если какие-то из них делают контекст грамматики синтаксического анализатора D чувствительным, но я верю, что нет. Напомним, я бы сказал, что грамматика D не зависит от контекста, а не LL (k) для любого k, и она имеет неприятную степень неоднозначности.

6 голосов
/ 08 августа 2011

Чтобы ответить на вопрос, является ли язык программирования контекстно-свободным, вы должны сначала решить, где провести границу между синтаксисом и семантикой. В качестве крайнего примера, в C недопустимо, чтобы программа использовала значение некоторых видов целых чисел после того, как им было разрешено переполнение. Очевидно, что это не может быть проверено во время компиляции, не говоря уже о времени разбора:

void Fn() {
  int i = INT_MAX;
  FnThatMightNotReturn();  // halting problem?
  i++;
  if(Test(i)) printf("Weeee!\n");
}

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

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

Учитывая, что наиболее правильной спецификацией для синтаксиса D является синтаксический анализатор (IIRC - синтаксический анализатор LL), я сильно подозреваю, что он фактически свободен от контекста согласно определению, которое я предложил.


Примечание: приведенное выше ничего не говорит о том, какую грамматику использует языковая документация или данный синтаксический анализатор, только если существует не зависящая от контекста грамматика. Кроме того, единственная полная документация по языку D - это исходный код компилятора DMD.

4 голосов
/ 08 августа 2011

От этих ответов у меня болит голова.

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

В C ++ (порядок может быть выключен, но это не должно лишать силы мою точку зрения):

  1. он должен обрабатывать макросы и другие материалы препроцессора
  2. он должен интерпретировать шаблоны
  3. наконец-то интерпретирует ваш код.

Поскольку первый шаг может изменить контекст второго шага, а второй шаг может изменить контекст третьего шага, язык, на котором ВЫ пишете (включая все эти шаги), является контекстно-зависимым.

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

Большинство языков в целом контекстно-зависимы, но у большинства языков есть только эти незначительные исключения, чтобы быть контекстно-свободными.

...