Нижче наведена моя (поточна) улюблена демонстрація того, чому аналіз C ++ є (ймовірно) Тюрінг-завершеним , оскільки він показує програму, яка синтаксично правильна тоді і лише тоді, коли задане ціле число є простим.
Тому я стверджую, що C ++ не є ні контекстним, ні контекстно-чутливим .
Якщо ви дозволяєте довільні послідовності символів з обох сторін будь-якого виробництва, ви створюєте граматику типу 0 ("необмежену") в ієрархії Хомського , яка є більш потужною, ніж граматика, залежна від контексту; граматики без обмежень є Тьюрінгом. Граматика, залежна від контексту (Type-1), допускає декілька символів контексту з лівого боку виробництва, але той самий контекст повинен з'являтися і в правій частині виробництва (звідси назва "контекстно-залежна"). [1] Контекстно-чутливі граматики еквівалентні лінійно обмеженим машинам Тьюрінга .
У прикладі програми основні обчислення можуть бути виконані лінійно-обмеженою машиною Тьюрінга, тому це не зовсім доводить еквівалентність Тьюрінга, але важлива частина полягає в тому, що аналізатору необхідно виконати обчислення, щоб провести синтаксичний аналіз. Це могло бути будь-яке обчислення, виражене як опис шаблону, і є всі підстави вважати, що екземпляр шаблону C ++ є Тюрінг-повним. Дивіться, наприклад, документ Тодда Л. Вельдхуйзена за 2003 рік .
Незважаючи на те, що C ++ може бути розібраний комп'ютером, тож його, безумовно, можна було б розібрати на машині Тьюрінга. Отже, граматика без обмежень могла це визнати. Насправді писати таку граматику було б недоцільно, тому стандарт не намагається цього робити. (Дивись нижче.)
Питання про «неоднозначність» певних виразів - це переважно червона оселедець. Для початку неоднозначність - це особливість певної граматики, а не мови. Навіть якщо можна довести, що мова не має однозначних граматик, якщо вона може бути розпізнана без граматики без контексту, вона є безконтекстною. Аналогічно, якщо вона не може бути розпізнана без контекстною граматикою, але її можна розпізнати за допомогою контекстно-чутливої граматики, це контекстно-залежно. Неоднозначність не має значення.
Але в будь-якому випадку, як і рядок 21 (тобто auto b = foo<IsPrime<234799>>::typen<1>();
) у нижченаведеній програмі, вирази зовсім не неоднозначні; їх просто розбирають по-різному в залежності від контексту. У найпростішому вираженні питання синтаксична категорія певних ідентифікаторів залежить від того, як вони були оголошені (наприклад, типи та функції), що означає, що формальна мова повинна була б визнати той факт, що два рядки довільної довжини в ті ж програми ідентичні (декларація та використання). Це можна змоделювати за допомогою граматики "копіювати", яка є граматикою, яка розпізнає дві послідовні точні копії одного і того ж слова. Це легко довести за допомогою насосної лемищо ця мова не є контекстною. Можлива контекстно-залежна граматика для цієї мови, і граматика типу 0 надається у відповіді на це питання: /math/163830/context-sensitive-grammar-for-the- мова копіювання .
Якби спробувати написати контекстно-залежну (або необмежену) граматику для розбору C ++, це цілком можливо наповнить Всесвіт писанкарами. Написання машини Тьюрінга для розбору C ++ було б не менш можливим завданням. Навіть писати програму C ++ складно, і, наскільки я знаю, жодна з них не виявилася правильною. Ось чому стандарт не намагається надати повну формальну граматику, і чому він вирішує записати деякі правила розбору на технічній англійській мові.
Те, що схоже на формальну граматику в стандарті C ++, не є повним формальним визначенням синтаксису мови C ++. Навіть не повне формальне визначення мови після попередньої обробки, що може бути простішим формалізувати. (Це не була б мова, однак: мова С ++, визначена стандартом, включає препроцесор, і функціонування препроцесора описано алгоритмічно, оскільки це було б надзвичайно важко описати в будь-якому граматичному формалізмі. Саме в цьому розділі стандарту, де описано лексичне розкладання, включаючи правила, коли його потрібно застосовувати не один раз.)
Різні граматики (дві грамати, що перекриваються для лексичного аналізу, одна, яка має місце перед попередньою обробкою, а інша, якщо необхідно, після цього, плюс "синтаксична" граматика), зібрані в Додатку А, з цим важливим записом (наголос додано):
Цей підсумок синтаксису 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
( |α|
означає "довжина α
")
Можна довести, що набір мов, розпізнаваних монотонними граматиками, точно такий же, як і набір мов, розпізнаваних контекстно-чутливими граматиками, і часто трапляється так, що простіше базувати докази на монотонних граматиках. Отже, досить часто бачимо, що "контекстно-чутливий" використовується так, ніби він означає "монотонне".