私は、C++が文脈依存言語であるという主張をよく聞きます。次の例を見てください。
a b(c);
これは変数定義ですか、それとも関数宣言ですか?それはシンボルc
の意味によって異なります。 c
がvariableの場合、a b(c);
はb
型のa
という名前の変数を定義します。それは直接c
で初期化されます。しかし、c
がtypeの場合、a b(c);
は、b
を取り、c
を返すa
という名前の関数を宣言します。
あなたが文脈自由言語の定義を調べるならば、それは基本的にすべての文法規則がちょうど1つの非終端記号からなる左側を持たなければならないことをあなたに言うでしょう。一方、文脈依存文法では、左側に終端記号と非終端記号の任意の文字列を使用できます。
「C++プログラミング言語」の付録Aを見てみると、左側に1つの非終端記号以外に何か他のものがある単一の文法規則を見つけることができませんでした。それはC++が文脈自由であることを意味するでしょう。 (もちろん、すべての文脈自由言語は文脈自由言語が文脈依存言語のサブセットを形成するという意味で文脈依存でもありますが、それはポイントではありません。)
それで、C++は文脈自由か文脈依存か?
以下は、C++の解析が(おそらく) Turing-complete である理由の私の(現在の)お気に入りのデモンストレーションです。なぜなら、与えられた整数が素数である場合に限り構文的に正しいプログラムを示すからです。
だから私はC++は文脈自由でも文脈依存でもないと主張しています。
任意の生成物の両側で任意のシンボルシーケンスを許可する場合は、 Chomsky階層 でType-0文法( "無制限")を生成します。これは、状況依存の文法よりも強力です。無制限の文法はチューリング完全です。状況依存(Type-1)文法では、プロダクションの左側に複数のコンテキストシンボルを使用できますが、プロダクションの右側に同じコンテキストを表示する必要があります(したがって、「コンテキストセンシティブ」という名前になります)。 [1]文脈依存文法は 線形有界チューリング機械 と等価です。
プログラム例では、素数計算は線形有界チューリング機械で実行できるので、チューリング等価性を完全に証明するものではありませんが、重要な部分は構文解析を実行するためにパーサーが計算を実行する必要があることです。テンプレートのインスタンス化として表現できる計算であれば、C++のテンプレートのインスタンス化はチューリング完全であると信じるすべての理由があります。例えば、 Todd L. Veldhuizenの2003年の論文 を参照してください。
それにもかかわらず、C++はコンピュータで解析できるので、Turingマシンで解析することも可能です。その結果、無制限の文法はそれを認識することができます。実際にそのような文法を書くことは実用的ではないでしょう、それは規格がそうしようとしない理由です。 (下記参照。)
特定の表現の「あいまいさ」に関する問題は、ほとんどがニシンです。まず、あいまいさは特定の文法の特徴であり、言語の特徴ではありません。たとえ言語が明確な文法を持たないことが証明されたとしても、文脈自由文法によって認識できれば文脈自由です。同様に、文脈自由文法では認識できないが文脈依存文法では認識できる場合は、文脈依存です。あいまいさは関係ありません。
しかし、いずれにせよ、以下のプログラムの21行目(つまりauto b = foo<IsPrime<234799>>::typen<1>();
)のように、式はまったくあいまいではありません。それらは文脈に応じて単に異なって解析されます。問題の最も簡単な表現では、特定の識別子の構文上のカテゴリは、それらがどのように宣言されているか(たとえば型や関数)に依存します。同じプログラムは同一です(宣言と使用)。これは、「コピー」文法によってモデル化できます。これは、同じ単語の連続した2つの正確なコピーを認識する文法です。この言語は文脈自由ではないことを ポンプ補題 と証明するのは簡単です。この言語の文脈依存文法が可能であり、この質問に対する答えとしてType-0文法が提供されています。 https://math.stackexchange.com/questions/163830/context-sensitive-grammar- for the the copy-language 。
C++をパースするために文脈依存の(または無制限の)文法を書き込もうとした場合、それはおそらく宇宙をスクリプティングで埋めるでしょう。 C++をパースするためにチューリングマシンを書くことも同様に不可能な仕事です。 C++プログラムを書くことさえ難しいです、そして私の知る限りではどれも正しいと証明されていません。これが規格が完全な形式文法を提供しようとしていない理由であり、なぜそれが技術英語でいくつかの解析規則を書くことを選択したのかです。
C++標準の正式な文法のように見えるのは、C++言語の構文の正式な正式定義ではありません。それは前処理の後の言語の完全な正式な定義でさえない、それは正式化するのがより簡単かもしれない。 (ただし、言語ではありません。標準で定義されているC++言語にはプリプロセッサが含まれています。プリプロセッサの動作は、文法的な形式では記述するのが非常に難しいため、アルゴリズム的に記述されています。レキシカル分解が記述されている標準の、それが一度以上適用されなければならない規則を含む)
さまざまな文法(字句解析のための2つの重なり合った文法、前処理の前に行われるもの、および必要に応じてその後に行われるもの、さらに「構文」文法)が、この重要な注意(強調が追加された)にまとめられています。
このC++構文の要約は、理解を助けるためのものです。 それは言語の正確な記述ではありません。特に、ここで説明されている文法は、 有効なC++構成体のスーパーセット。式と宣言を区別するために、曖昧さ除去規則(6.8、7.1、10.2)を適用する必要があります。さらに、アクセス制御、あいまいさ、および型規則を使用して、構文的には有効だが意味のない構成要素を排除する必要があります。
最後に、これが約束のプログラムです。 IsPrime<N>
のNが素数である場合に限り、21行目は構文的に正しいです。それ以外の場合、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 → γ
でのみ[α, β]
と読み替えることができます。文脈自由(Type 2)文法では、α
とβ
は空でなければなりません。
すべての生成物が次の形式でなければならない「単調」制限を使用して文法を制限することもできます。
α → β
ここで|α| ≥ |β| > 0
(|α|
は「α
の長さ」を意味します)
単調な文法によって認識される言語の集合は文脈依存の文法によって認識される言語の集合と全く同じであることを証明することは可能であり、そして単調な文法に基づく証明がより容易である場合が多い。その結果、あたかも「単調」を意味するかのように「文脈依存」が使用されるのを見るのはかなり一般的です。
まず、C++標準の終わりには文法に文脈依存の規則がないことに気づいたので、文法is文脈自由です。
ただし、その文法はC++言語を正確には記述していません。
int m() { m++; }
または
typedef static int int;
「整形式のC++プログラムのセット」として定義されているC++言語は、文脈自由ではありません(宣言される変数を要求するだけであることを示すことは可能です)。理論的にはチューリング完全プログラムをテンプレートで記述し、その結果に基づいてプログラムを不正な形式にすることができるとすれば、状況依存でさえありません。
現在、(無知な)人々(通常は言語理論家ではないが、パーサ設計者)は、通常、次のような意味で「文脈自由ではない」を使用する。
標準の後ろの文法はこれらのカテゴリを満たしていません(つまり、あいまいで、LL(k)...ではありません)ので、C++の文法は「文脈自由」ではありません。そしてある意味では、それらは正常に機能するC++パーサーを作成するのは難しいことです。
ここで使用されている特性は文脈自由言語との関連性が低いだけであることに注意してください - あいまいさは文脈依存性とは何の関係もありません。フリーの言語。そして、文脈自由言語の構文解析は線形プロセスではありません(決定論的言語の構文解析はそうですが)。
はい。次の式は、型解決されたコンテキストによって異なる操作の順序を持ちます。
編集:実際の操作の順番が変わったとき、それを装飾する前に装飾されていない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 );
あなたの質問に答えるためには、2つの異なる質問を区別する必要があります。
ほとんどすべてのプログラミング言語の単なる構文は文脈自由です。通常、それは拡張バッカスナウア形または文脈自由文法として与えられる。
ただし、プログラムがプログラミング言語で定義されている文脈自由文法に準拠していても、必ずしも有効プログラムとは限りません。プログラムが有効なプログラムになるために満たさなければならない多くの非コンテキストフリーの性質があります。たとえば、最も単純なそのようなプロパティは変数のスコープです。
結論として、C++が文脈自由であるかどうかはあなたが尋ねる質問によります。
Bjarne Stroustrupによる C++の設計と進化 をご覧ください。その中で彼は、初期のバージョンのC++を解析するためにyacc(または同様のもの)を使おうとし、代わりに再帰的降下を使用したことを望んでいる彼の問題について説明しています。
ええC++は文脈依存、非常に文脈依存です。コンテキストフリーパーサーを使用してファイルを単純に解析して構文ツリーを構築することはできません。場合によっては、決定するために以前の知識からシンボルを知る必要があるためです(つまり、解析中にシンボルテーブルを構築します)。
最初の例:
A*B;
これは乗算式ですか?
OR
これはB
変数の宣言で、型A
のポインターになりますか?
Aが変数の場合は式、Aが型の場合はポインタ宣言です。
2番目の例:
A B(bar);
これはbar
型の引数を取る関数プロトタイプですか?
OR
これはB
型の変数A
を宣言し、初期化子としてbar
を定数としてAのコンストラクタを呼び出しますか?
bar
が変数かシンボルテーブルの型かどうかをもう一度知る必要があります。
3番目の例:
class Foo
{
public:
void fn(){x*y;}
int x, y;
};
これは、xとyの宣言が関数定義の後にくるため、構文解析中にシンボルテーブルを作成しても役に立たない場合です。したがって、最初にクラス定義を調べ、2回目のパスでメソッド定義を調べて、x * yが式でありポインタ宣言などではないことを伝える必要があります。
C++はGLRパーサーで解析されます。つまり、ソースコードの解析中に、パーサーmayがあいまいになりますが、続行して、使用する文法規則を決定する必要がありますlater。
また見て、
文脈自由文法はできないことを覚えておいてくださいルールを記述するALLプログラミング言語の構文。たとえば、属性の文法は、式の種類の有効性を確認するために使用されます。
int x;
x = 9 + 1.0;
できません文脈自由文法で以下のルールを記述します:割り当ての右側は左側と同じタイプでなければなりません。
「文脈依存」の正式な定義と「文脈依存」の非公式な使用の間には混乱があるようです。前者は明確な意味を持ちます。後者は、「入力を解析するためにコンテキストが必要です」と言うために使用されます。
これはまたここで頼まれます: 文脈敏感さvsあいまいさ 。
これが文脈自由文法です。
<a> ::= <b> | <c>
<b> ::= "x"
<c> ::= "x"
あいまいなので、入力 "x"を解析するためには何らかの文脈が必要です(またはあいまいさを持って生きる、または "警告:E8271 - 入力が115行目であいまいです")。しかし、それは確かに文脈依存の文法ではありません。
文脈自由文法の最も単純なケースは、テンプレートを含む式を解析することです。
a<b<c>()
これは次のどちらかとして解析できます。
template
|
a < expr > ()
|
<
/ \
b c
または
expr
|
<
/ \
a template
|
b < expr > ()
|
c
2つのASTは、 'a'の宣言を調べることによってのみ曖昧さをなくすことができます - 'a'がテンプレートの場合は前者AST、そうでない場合は後者。
ALGOLのような言語は文脈自由ではありません、なぜならそれらはそれらの型に基づいて識別子が現れることができる式やステートメントを制限するルールを持ち、そして宣言と使用の間に発生できるステートメントの数に制限がないからです。
通常の解決策は、実際に有効なプログラムのスーパーセットを受け入れ、文脈依存部分をアドホック「セマンティック」コードをルールに付加した文脈自由パーサを書くことです。
チューリング完全テンプレートシステムのおかげで、C++はこれをはるかに超えています。 Stack Overflow Question 794015 を参照してください。
C++標準のプロダクションはコンテキストフリーで書かれていますが、私たち全員が知っているように、言語を正確に定義しているわけではありません。現在の言語ではほとんどの人があいまいさと見なしているものの中には、文脈依存の文法で明確に解決できるものもあると思います(私は信じています)。
最も明白な例として、Most Vexing Parse: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として適格ではないということです。
a b(c);
は2つの有効な解析 - 宣言と変数 - を持つので、文脈依存です。 「c
が型の場合」と言うと、その文脈です。C++がそれに対してどのように敏感であるかを正確に説明しました。 「c
とは何ですか?」という文脈がなかったらこれを明確に解析することはできませんでした。
ここでは、コンテキストはトークンの選択で表現されます。パーサーは、タイプを指定する場合、識別子をタイプ名トークンとして読み取ります。これが最も簡単な解決方法であり、(この場合は)状況依存であることによる複雑さの多くを回避します。
編集:もちろん、文脈依存性の問題がもっとあります、私はあなたが示したものに単に焦点を合わせました。テンプレートはこれにとって特に厄介です。
本当:)
J.スタンレーウォーフォード。 コンピュータシステム 。 341 - 346ページ。
C++テンプレートはTuring Powerfulであることが証明されています。正式な参考文献ではありませんが、ここでその点を検討する場所です。
http://cpptruths.blogspot.com/2005/11/c-templates-are-turing-complete.html
私は推測(60年代のALGOLがCFGによって表現されることができなかったことを示す民俗的で簡潔なCACM証明と同じくらい古くから)、そしてそれ故C++がCFGだけによって正しく解析されることができないと言うでしょう。ツリーパスまたは削減イベント中のさまざまなTPメカニズムと組み合わせたCFG - これは別の話です。一般的な意味では、停止問題のために、正しい/正しくないことを示すことができないがそれでも正しい/正しくないC++プログラムが存在します。
{PS- Meta-Sの作者として(上記の何人かの人々によって言及されている) - 私はThothicが消滅したものでもなく、ソフトウェアが無料で入手可能でもないと最も確実に言うことができる。おそらく、私は自分の回答のこのバージョンを、削除されたり-3に投票したりしないように表現しました。}
時にはそれがさらに悪い場合があります。 C++に「決定不可能な文法」があると人々が言うのはどういう意味ですか?
C++はコンテキストフリーではありません。私はしばらく前にコンパイラの講義でそれを学びました。簡単な検索でこのリンクが表示されました。「構文またはセマンティクス」のセクションで、CとC++が文脈自由ではない理由を説明しています。
よろしく、
卵子
Meta-S "はQuinn Tyler Jacksonによる文脈依存解析エンジンです。私はそれを使ったことはありませんが、彼は印象的な話をしています。comp.compilersで彼のコメントをチェックしてください。 - Ira Baxter 7月25日10:42
正しいリンクは 解析エンジン です。
Meta-SはThothicと呼ばれる現存しない会社の財産です。私は興味のある人なら誰にでもMeta-Sの無料コピーを送ることができ、私はそれをrna構文解析研究に使った。 examplesフォルダに含まれている "pseudoknot grammar"は、バイオインフォマティクスのない、成熟したプログラマによって書かれたもので、基本的には機能しません。私の文法は異なるアプローチを取り、とてもうまくいきます。
明らかに、あなたが逐語的に質問をするならば、識別子を持つほとんどすべての言語は文脈依存的です。
識別子が正しく使用できるようにするには、識別子が型名(クラス名、typedefによって導入された名前、typenameテンプレートパラメータ)、テンプレート名、またはその他の名前であるかどうかを知る必要があります。例えば:
x = (name)(expression);
name
が型名の場合はキャスト、name
が関数名の場合は関数呼び出しです。もう1つのケースは、変数定義と関数宣言を区別することが不可能な、いわゆる "最も厄介な解析"です(関数宣言であると言う規則があります)。
そのため、依存名を持つtypename
およびtemplate
が必要になりました。私が知る限り、C++の他の部分は文脈依存ではありません(つまり、文脈自由文法を書くことは可能です)。
ここでの大きな問題は、「コンテキストフリー」および「コンテキストセンシティブ」という用語がコンピューターサイエンスの中で少し直感的でないことです。 C++の場合、コンテキスト依存性はあいまいさのように見えますが、一般的な場合には必ずしもそうではありません。
C/++では、ifステートメントは関数本体内でのみ許可されます。それはそれをコンテキスト依存にするように見えるでしょう?うーん、ダメ。文脈自由文法は、実際にはコードの一部を抜き取り、それが有効かどうかを判断できるプロパティを必要としません。それは実際にはコンテキストフリーの意味ではありません。それは実際には、それがどのように聞こえるかに関連する何らかの種類を漠然と暗示している単なるラベルです。
現在、関数本体内のステートメントが、a * b;
の場合のように、直接の文法上の先祖の外側で定義されたもの(識別子が型または変数を記述するかどうか)に応じて異なる方法で解析される場合、実際にはコンテキストです-敏感。ここには実際のあいまいさはありません。 a
が型の場合はポインターの宣言として解析され、それ以外の場合は乗算として解析されます。
状況依存であることは、必ずしも「解析が難しい」という意味ではありません。悪名高いa * b;
"曖昧さ"は、以前に遭遇したtypedef
sを含むシンボルテーブルで解決できるため、Cは実際にはそれほど難しくありません。 C++がときどき行うようなケースを解決するために、任意のテンプレートのインスタンス化(チューリング完全であることが証明されている)は必要ありません。実際には、C++と同じ状況依存性を持っているにもかかわらず、有限の時間でコンパイルしないCプログラムを書くことはできません。
Python(および他の空白に敏感な言語)もインデントおよびデデントトークンを生成するためにレクサーの状態を必要とするため、コンテキストに依存しますが、それは典型的なLL-1文法よりも解析を難しくしません。実際にはパーサージェネレーターを使用しているため、Pythonにこのような情報のない構文エラーメッセージが表示されます。 Pythonにはa * b;
問題のような「あいまいさ」がないことに注意することも重要です。これは、「あいまいな」文法(最初の段落で述べた)のない状況依存言語の良い具体例を示しています。