it-swarm.com.ru

Является ли C ++ контекстно-зависимым или контекстно-зависимым?

Я часто слышу заявления о том, что C++ является контекстно-зависимым языком. Возьмите следующий пример:

a b(c);

Это определение переменной или объявление функции? Это зависит от значения символа c. Если c является переменная, то a b(c); определяет переменную с именем b типа a. Он напрямую инициализируется с помощью c. Но если c является тип, то a b(c); объявляет функцию с именем b, которая принимает c и возвращает a.

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

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

Итак, является ли C++ контекстно-зависимым или контекстно-зависимым?

389
fredoverflow

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

Поэтому я утверждаю, что C++ не является ни контекстно-зависимым, ни контекстно-зависимым .

Если вы разрешаете произвольные последовательности символов на обеих сторонах любого производства, вы создаете грамматику типа 0 ("неограниченную") в иерархия Хомского , которая является более мощной, чем контекстно-зависимая грамматика; неограниченные грамматики полны по Тьюрингу. Контекстно-зависимая (Тип-1) грамматика допускает несколько символов контекста в левой части продукции, но тот же контекст должен появляться в правой части продукции (отсюда и название "контекстно-зависимая"). [1] Контекстно-зависимые грамматики эквивалентны линейно ограниченные машины Тьюринга .

В примере программы простое вычисление может быть выполнено с помощью линейно ограниченной машины Тьюринга, поэтому она не вполне доказывает эквивалентность Тьюринга, но важная часть заключается в том, что синтаксическому анализатору необходимо выполнить вычисления для выполнения синтаксического анализа. Это могли быть любые вычисления, выражаемые как создание экземпляра шаблона, и есть все основания полагать, что создание экземпляра шаблона C++ является тьюрингово полным. См., Например, статья Тодда Л. Вельдхуйзена за 2003 год .

Независимо от этого, C++ может быть проанализирован компьютером, поэтому он, безусловно, может быть проанализирован машиной Тьюринга. Следовательно, неограниченная грамматика может распознать это. На самом деле написание такой грамматики было бы непрактичным, поэтому стандарт не пытается это сделать. (Увидеть ниже.)

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

Но в любом случае, как в строке 21 (т.е. auto b = foo<IsPrime<234799>>::typen<1>();) в программе ниже, выражения вовсе не являются двусмысленными; они просто анализируются по-разному в зависимости от контекста. В самом простом выражении проблемы синтаксическая категория определенных идентификаторов зависит от того, как они были объявлены (например, типы и функции), что означает, что формальный язык должен будет признать тот факт, что две строки произвольной длины в одна и та же программа идентична (декларация и использование). Это может быть смоделировано грамматикой "копирования", которая является грамматикой, которая распознает две последовательные точные копии одного и того же Слова. С помощью прокачивая лемма легко доказать, что этот язык не является контекстно-свободным. Для этого языка возможна контекстно-зависимая грамматика, а в ответе на этот вопрос приведена грамматика типа 0: https://math.stackexchange.com/questions/163830/context-sensitive-grammar- for-the-copy-language .

Если бы кто-то попытался написать контекстно-зависимую (или неограниченную) грамматику для синтаксического анализа C++, вполне возможно, что он заполнит вселенную строчками. Написание машины Тьюринга для разбора C++ было бы столь же невозможным делом. Даже написание программы на C++ затруднительно, и, насколько я знаю, ни одна из них не оказалась верной. Вот почему стандарт не пытается предоставить полную формальную грамматику и почему он предпочитает писать некоторые правила синтаксического анализа на техническом английском языке.

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

Различные грамматики (две накладывающиеся грамматики для лексического анализа, одна из которых имеет место перед предварительной обработкой, а другая, если необходимо, впоследствии, плюс "синтаксическая" грамматика), собраны в Приложении A с этим важным примечанием (выделение добавлено):

Это краткое изложение синтаксиса C++ предназначено для помощи в понимании. Это не точное утверждение языка, В частности, грамматика, описанная здесь, принимает расширенный набор допустимых конструкций C++, Правила устранения неоднозначности (6.8, 7.1, 10.2) должны применяться, чтобы отличать выражения от объявлений. Кроме того, для исключения синтаксически допустимых, но бессмысленных конструкций необходимо использовать правила контроля доступа, неоднозначности и типа.

Наконец, вот обещанная программа. Строка 21 синтаксически правильна тогда и только тогда, когда N в IsPrime<N> простое. В противном случае typen является целым числом, а не шаблоном, поэтому typen<1>() анализируется как (typen<1)>(), что синтаксически неверно, поскольку () не является синтаксически допустимым выражением.

template<bool V> struct answer { answer(int) {} bool operator()(){return V;}};

template<bool no, bool yes, int f, int p> struct IsPrimeHelper
  : IsPrimeHelper<p % f == 0, f * f >= p, f + 2, p> {};
template<bool yes, int f, int p> struct IsPrimeHelper<true, yes, f, p> { using type = answer<false>; };
template<int f, int p> struct IsPrimeHelper<false, true, f, p> { using type = answer<true>; };

template<int I> using IsPrime = typename IsPrimeHelper<!(I&1), false, 3, I>::type;
template<int I>
struct X { static const int i = I; int a[i]; }; 

template<typename A> struct foo;
template<>struct foo<answer<true>>{
  template<int I> using typen = X<I>;
};
template<> struct foo<answer<false>>{
  static const int typen = 0;
};

int main() {
  auto b = foo<IsPrime<234799>>::typen<1>(); // Syntax error if not prime
  return 0;
}

[1] Чтобы выразить это более технически, каждое производство в контекстно-зависимой грамматике должно иметь форму:

αAβ → αγβ

где A не является терминальным, а α, β возможно являются пустыми последовательностями грамматических символов, а γ является непустой последовательностью. (Символы грамматики могут быть терминалами или нетерминалами).

Это можно прочитать как A → γ только в контексте [α, β]. В грамматике без контекста (Тип 2) α и β должны быть пустыми.

Оказывается, вы также можете ограничить грамматику с помощью "монотонного" ограничения, где каждое произведение должно иметь форму:

α → β где |α| ≥ |β| > 0 (|α| означает "длина α")

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

329
rici

Во-первых, вы правильно заметили, что в грамматике в конце стандарта C++ нет контекстно-зависимых правил, так что грамматика is не зависит от контекста.

Однако эта грамматика точно не описывает язык C++, потому что она производит программы не на C++, такие как

int m() { m++; }

или же

typedef static int int;

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

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

  • двусмысленный
  • нельзя разобрать с бизоном
  • не LL (k), LR (k), LALR (k) или любой другой языковой класс, определенный парсером, который они выбрали

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

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

112
jpalecek

Да. Следующее выражение имеет другой порядок операций в зависимости от тип разрешенного контекста:

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

#if FIRST_MEANING
   template<bool B>
   class foo
   { };
#else
   static const int foo = 0;
   static const int bar = 15;
#endif

С последующим:

static int foobar( foo < 2 ? 1 < 1 : 0 > & bar );
60
Sam Harwell

Чтобы ответить на ваш вопрос, вам нужно выделить два разных вопроса.

  1. Простой синтаксис почти каждого языка программирования не зависит от контекста. Как правило, это дается в виде расширенной формы Бэкуса-Наура или грамматики без контекста.

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

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

24
Dan

Возможно, вы захотите взглянуть на Дизайн и эволюция C++ Бьярна Страуструпа. В нем он описывает свои проблемы, пытаясь использовать yacc (или аналогичный) для анализа ранней версии C++, и желая, чтобы он использовал вместо этого рекурсивный спуск.

12
anon

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

Первый пример:

A*B;

Это выражение умножения?

OR

Является ли это объявлением переменной B в качестве указателя типа A?

Если A - переменная, то это выражение, если A - тип, это объявление указателя.

Второй пример:

A B(bar);

Это прототип функции, принимающий аргумент типа bar?

OR

Является ли это объявлением переменной B типа A и вызывает ли конструктор А с константой bar в качестве инициализатора?

Вы должны снова знать, является ли bar переменной или типом из таблицы символов.

Третий пример:

class Foo
{
public:
    void fn(){x*y;}
    int x, y;
};

Это тот случай, когда построение таблицы символов во время синтаксического анализа не помогает, потому что объявление x и y идет после определения функции. Поэтому вам нужно сначала просмотреть определение класса и просмотреть определения методов во втором проходе, чтобы сказать, что x * y - это выражение, а не объявление указателя или что-то еще.

11
Calmarius

C++ анализируется с помощью анализатора GLR. Это означает, что во время синтаксического анализа исходного кода анализатор может столкнуться с неоднозначностью, но он должен продолжить и решить, какое грамматическое правило использовать позже .

смотри также,

Почему C++ не может быть проанализирован парсером LR (1)?


Помните, что контекстно-свободная грамматика не может описать ВСЕ правила синтаксиса языка программирования. Например, грамматика атрибута используется для проверки правильности типа выражения.

int x;
x = 9 + 1.0;

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

10
AraK

У меня есть ощущение, что существует некоторая путаница между формальным определением "контекстно-зависимым" и неформальным использованием "контекстно-зависимым". Первый имеет четко определенный смысл. Последний используется для выражения "вам нужен контекст для анализа входных данных".

Это также спрашивается здесь: Чувствительность к контексту против неоднозначности .

Вот контекстно-свободная грамматика:

<a> ::= <b> | <c>
<b> ::= "x"
<c> ::= "x"

Это неоднозначно, поэтому для анализа входных данных "х" вам нужен некоторый контекст (или жить с неоднозначностью, или выдать "Предупреждение: E8271 - Ввод строки неоднозначен в строке 115"). Но это определенно не контекстно-зависимая грамматика.

9
Omri Barel

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

a<b<c>()

Это можно разобрать как

template
   |
   a < expr > ()
        |
        <
      /   \
     b     c

Или же

 expr
   |
   <
 /   \
a   template
     |
     b < expr > ()
          |
          c

С двумя AST можно избавиться от неоднозначности, только проверив объявление 'a' - первое AST, если 'a' - шаблон, или второе, если нет.

6
Aaron

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

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

C++ выходит далеко за рамки этого благодаря своей полной шаблонной системе Turing. Смотрите Вопрос о переполнении стека 794015 .

6
James Jones

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

Для наиболее очевидного примера давайте рассмотрим самый сложный анализ: int f(X);. Если X является значением, то это определяет f как переменную, которая будет инициализирована с X. Если X является типом, он определяет f как функцию, принимающую один параметр типа X.

Глядя на это с грамматической точки зрения, мы можем рассмотреть это так:

A variable_decl ::= <type> <identifier> '(' initializer ')' ';'

B function_decl ::= <type> <identifier> '(' param_decl ')' ';'

A ::= [declaration of X as value]
B ::= [declaration of X as type]

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

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

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

5
Jerry Coffin

Это зависит от контекста, так как a b(c); имеет два допустимых синтаксических анализа - объявление и переменную. Когда вы говорите "Если c - это тип", это контекст, прямо здесь, и вы точно описали, насколько C++ чувствителен к нему. Если у вас не было этого контекста "Что такое c?" Вы не могли бы разобрать это однозначно.

Здесь контекст выражается в выборе токенов - синтаксический анализатор считывает идентификатор как токен имени типа, если он называет тип. Это простейшее решение, и оно позволяет избежать значительной сложности, связанной с контекстом (в данном случае).

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

5
Puppy

Правда :)

Дж. Стэнли Уорфорд. Компьютерные системы . Стр. 341-346.

5
anno

Было показано, что шаблоны C++ являются мощными по Тьюрингу. Хотя это и не формальная ссылка, здесь есть место, чтобы посмотреть в этом отношении:

http://cpptruths.blogspot.com/2005/11/c-templates-are-turing-complete.html

Я рискну предположить (столь же старый, как фольклорное и краткое доказательство CACM, показывающее, что ALGOL в 60-х годах не может быть представлен CFG), и скажу, что C++ не может быть правильно проанализирован только CFG. CFGs, в сочетании с различными механизмами TP в проходе дерева или во время редукционных событий - это другая история. В общем смысле, из-за проблемы остановки, существует некоторая программа на C++, которая не может быть показана правильной/неправильной, но тем не менее правильной/неправильной.

{PS- Как автор Meta-S (упомянутый несколькими людьми выше) - я могу с уверенностью сказать, что Thothic не является более не существующей, и программное обеспечение не доступно бесплатно. Возможно, я сформулировал эту версию своего ответа так, чтобы меня не удаляли и не голосовали до -3.}

4
Quinn Tyler Jackson
4
sdcvvc

C++ не является контекстно-свободным. Я узнал это некоторое время назад в лекции компиляторов. Быстрый поиск дал эту ссылку, где раздел "Синтаксис или семантика" объясняет, почему C и C++ не являются контекстно-свободными:

Обсуждение Википедии: грамматика без контекста

С Уважением,
Ованес

3
ovanes

Meta-S "является контекстно-зависимым механизмом парсинга Куинн Тайлер Джексон. Я не использовал его, но он рассказывает впечатляющую историю. Посмотрите его комментарии в comp.compilers и посмотрите rnaparse.com/MetaS%20defined.htm - Ира Бакстер 25 июля в 10:42

Правильная ссылка парсинг enigines

Meta-S был собственностью несуществующей компании под названием Thothic. Я могу отправить бесплатную копию Meta-S всем, кто интересуется ею, и я использовал ее в исследованиях анализа РНА. Обратите внимание, что "грамматика псевдоузла", включенная в папки с примерами, была написана программистом, не занимающимся биоинформатикой, и в основном не работает. Мои грамматики используют другой подход и работают довольно хорошо.

2
user175479

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

Необходимо знать, является ли идентификатор именем типа (именем класса, именем, введенным typedef, параметром шаблона typename), именем шаблона или каким-либо другим именем, чтобы можно было правильно использовать идентификатор. Например:

x = (name)(expression);

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

Эта трудность привела к необходимости typename и template с зависимыми именами. Насколько я знаю, остальная часть C++ не является контекстно-зависимой (то есть для нее можно написать контекстно-свободную грамматику).

2
AProgrammer