it-swarm.com.ru

Действительно ли грамматика D не зависит от контекста?

Я опубликовал это в группе новостей 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, просто взглянув на него (т.е. это может быть число, это может быть тип данных, или это может быть Tuple of God-знает-что). Я даже не могу сказать, является ли выражение грамматически допустимым (поскольку int[U], конечно, нет - у вас не может быть хеш-таблицы с кортежами в качестве ключей или значений).

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

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

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


Обновление:

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

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

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

58
Mehrdad

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

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

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

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

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

42
AProgrammer

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

По сути, это означает, что определение элемента языка не может меняться в зависимости от того, какие элементы его окружают. Под элементами языка я имею в виду такие понятия, как выражения и идентификаторы , а не конкретные экземпляры. из этих понятий внутри программ, таких как 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, когда вы вызывать шаблоны функций-членов.

21
Nicola Musatti

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

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

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 в объявлении соответствует вычисляемому.

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

  • Проверка типа
  • Количество аргументов для функции
  • значение по умолчанию для функции
  • если 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 в какой-то момент должен проверить, как что-то определено в другом месте, просто сказать, что программа структурно правильна, то ее грамматика не является контекстно-свободной. Если вы изолируете какую-либо часть кода и он все еще может сказать, что он структурно корректен, то он не зависит от контекста.

9
Shahbaz

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

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

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

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

8
Puppy

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

string ::= q" Delim1 Chars newline Delim2 "

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

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

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

7
Ellery Newcomer

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

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

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

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

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


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

6
BCS

Эти ответы делают мою голову болит.

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

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

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

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

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

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

4
Jason McCarrell