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