以下は、C ++の構文解析が(おそらく)チューリング完全である理由の私の(現在の)お気に入りのデモンストレーションです。これは、指定された整数が素数である場合にのみ構文的に正しいプログラムを示しているためです。
したがって、 C++は文脈自由でも文脈依存でもないと断言します。
プロダクションの両側で任意のシンボルシーケンスを許可すると、Chomsky階層でタイプ0の文法(「無制限」)が生成されます。これは、状況依存の文法よりも強力です。無制限文法はチューリング完全です。状況依存(タイプ1)文法では、プロダクションの左側に複数のコンテキストシンボルを使用できますが、同じコンテキストをプロダクションの右側に表示する必要があります(そのため「コンテキスト依存」という名前が付けられています)。[1]状況依存の文法は、線形拘束チューリングマシンと同等です。
サンプルプログラムでは、プライム計算は線形拘束チューリングマシンで実行できるため、チューリングの同等性を完全に証明することはできませんが、構文解析を実行するためにパーサーが計算を実行する必要があることが重要です。テンプレートのインスタンス化として表現できる計算であれば、C++テンプレートのインスタンス化がチューリング完全であると信じる理由は十分にあります。たとえば、ToddL.Veldhuizenの2003年の論文を参照してください。
とにかく、C ++はコンピューターで解析できるので、チューリングマシンでも確実に解析できます。その結果、無制限文法はそれを認識することができた。実際にそのような文法を書くことは非現実的であり、それが標準がそうしようとしない理由です。(下記参照。)
特定の表現の「あいまいさ」の問題は、ほとんどが赤いニシンです。そもそも、あいまいさは特定の文法の特徴であり、言語ではありません。言語に曖昧さのない文法がないことが証明できたとしても、文脈自由文法で認識できれば、文脈自由です。同様に、文脈自由文法では認識できないが、文脈依存文法では認識できる場合は、文脈依存です。あいまいさは関係ありません。
しかし、いずれにしても、auto b = foo<IsPrime<234799>>::typen<1>();
以下のプログラムの21行目(つまり)のように、式はまったくあいまいではありません。それらは、コンテキストに応じて単純に異なる方法で解析されます。問題の最も単純な表現では、特定の識別子の構文カテゴリは、それらがどのように宣言されているか(タイプや関数など)に依存します。つまり、形式言語は、2つの任意の長さの文字列が同じプログラムは同一です(宣言と使用)。これは、「コピー」文法によってモデル化できます。これは、同じ単語の2つの連続する正確なコピーを認識する文法です。ポンピング補題で証明するのは簡単ですこの言語は文脈自由ではありません。この言語の状況依存文法が可能であり、タイプ0の文法がこの質問への回答で提供されています:https ://math.stackexchange.com/questions/163830/context-sensitive-grammar-for-the-コピー言語。
C ++を解析するために文脈依存(または無制限)文法を書き込もうとすると、おそらく宇宙は落書きでいっぱいになります。C ++を解析するチューリングマシンを作成することも、同様に不可能な作業です。C ++プログラムを書くことさえ困難であり、私が知る限り、どれも正しいことが証明されていません。これが、標準が完全な形式文法を提供しようとしない理由であり、解析規則の一部を技術英語で記述することを選択する理由です。
C ++標準の形式文法のように見えるのは、C++言語の構文の完全な形式定義ではありません。前処理後の言語の完全な正式な定義でさえありません。これは、形式化するのが簡単な場合があります。(ただし、それは言語ではありません。標準で定義されているC ++言語にはプリプロセッサが含まれており、文法的な形式で説明するのは非常に難しいため、プリプロセッサの動作はアルゴリズムで説明されています。このセクションにあります。語彙分解が記述されている標準の、それが複数回適用されなければならない規則を含む。)
さまざまな文法(字句解析用の2つの重複する文法、1つは前処理の前に行われ、もう1つは必要に応じて後で行われる)と「構文」文法が付録Aにまとめられており、この重要な注意事項があります(強調を追加)。
このC++構文の要約は、理解を助けることを目的としています。それは言語の正確な記述ではありません。特に、ここで説明する文法は、有効なC++構造のスーパーセットを受け入れます。式と宣言を区別するには、曖昧性解消規則(6.8、7.1、10.2)を適用する必要があります。さらに、アクセス制御、あいまいさ、および型の規則を使用して、構文的には有効であるが意味のない構造を取り除く必要があります。
最後に、これが約束されたプログラムです。IsPrime<N>
21行目は、N inが素数である場合にのみ、構文的に正しいです。それ以外の場合、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
(|α|
は「の長さ」を意味しますα
)
単調文法によって認識される言語のセットが状況依存文法によって認識される言語のセットとまったく同じであることを証明することは可能であり、単調文法に基づいて証明する方が簡単な場合がよくあります。したがって、「単調」を意味するかのように「コンテキスト依存」が使用されるのはかなり一般的です。