1.文法
ジェネレーターは、RBNFルールに従ってコードを生成します。 なぜなら パーサーは再帰下降の価値があり、これは上から下へ構文解析するため、文法はLL(k)に属します。 理想的にはLL(1)ですが、競合の解決はcoco / rで行われます。
LL(1)の競合は、セマンティックアクションと先のトークンの表示によって解決されます。
2つの異なる製品S-> A | Bの条件が満たされる場合、文法はLL(1)です。
1. AとBがaで始まる行を生成する端末aはありません。
2.空の行は、製品AまたはBのいずれか1つによって生成される場合があります。
3. eがセットFIRST(B)に属している場合、セットFIRST(A)FOLLOW(S)-交差しません。 FIRST(A)に属するeについての同様の記述も当てはまります。
2.言語ココ/ R
ジェネレーターは、java、C ++、C#、F#、VB.Net、Oberonの言語でコードを作成できます。
この例では、C ++が使用されています。
すべてのルールはピリオドで終了する必要があります。
S-> ABという形式の製品は、S = A Bと記述されます。
| -または
()-グループ
[]-0または1
{}-0以上
2.1インポート、つまり ヘッダーの接続。
#include <string> #include <sstream> #include <iostream> #include <fstream> #include <vector>
2.2コンパイラID 、つまり COMPILERキーワードの後に、文法が始まる非終端記号が続きます。
コンパイラexpr
2.3グローバル変数と関数。
COMPILERキーワードの後に、変数と関数を含むセクションを挿入できます。 多くの関数がある場合は、それらを別のファイルに配置することをお勧めします。 そして、それらがなければ、文法は非常に怖くて判読できなくなります。
int toInt(const std::wstring& strbuf) { std::wstringstream converter; int value = 0; converter << strbuf; converter >> value; return value; } std::wstring toString ( int Number ) { std::wostringstream ss; ss << Number; return ss.str(); }
2.4スキャナーを生成するためのルール。
2.4.1大文字と小文字を区別しないIGNORECASEキーワード。
2.4.2文字-有効な文字のセットを説明するセクション。 たとえば、私たちにとっての手紙とは何ですか。
チャーター
letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"。
桁= "0123456789"。
区切り文字もここで説明できます。
cr = '\ r'。
lf = '\ n'。
tab = '\ t'。
char 1 .. char2という形式の表記は、char1からchar2までの文字のシーケンスを示します。
桁= '0' .. '9'。
ANY-文字のシーケンス。
+多くを含む
-含まない
たとえば、二重引用符を含まない多くの文字を記述することができます。
verbatimStringChar = ANY-'"'。
16進表記の文字セット:
hexDigit = digit + "ABCDEFabcdef"。
2.4.3トークン自体、または別の名前-トークン。 セクションキーワードはTOKENSです。 識別子トークンと数字トークンの意味を説明します。
トークン
ident = letter {letter | 桁| 「_」}。
番号=桁{桁}。
二重引用符で囲まれた一連の文字のような文字列トークン:
文字列= "\" "{verbatimStringChar}" \ ""。
16進数:
hex = "0x" {hexDigit hexDigit}。
2.4.4特別なオプションのセクション。
2.4.5コメント。
「/ *」から「* /」へのコメントのネスト
「//」からcr lfへのコメント
2.4.6セパレーター。 無視するセパレーターを記述します。
IGNORE cr + lf +タブ
2.5パーサーを生成するためのルール。
PRODUCTIONSキーワードから始めます。
式の文法の例:
Expr = ident ':=' NumExpr ';'
NumExpr = Term {( '+' | '-')NumExpr}
Term = Multiplier {( '*' | '/')Term}
乗数= ident | 番号| '(' NumExpr ')'
すべての属性は角括弧<>で書かれています。 リストなどのstlから何かを使用する場合、属性はドット付きの括弧内に配置する必要があります。 <。 。>。
属性は非端末で書き込まれ、関数パラメーターとして変換されます。
ターミナルには属性はありませんが、本当に必要な場合は、非ターミナルでラップできます。
LogOp <std :: wstring&op> =( "&&" | "||")(.op = t-> val;。)。
セマンティックアクションは、ドット(..)付きの括弧内に記述されます。 これは、ジェネレーターがパーサーに挿入するコードです。 コードは、レクサーとパーサーが生成される言語で書かれています。
ANY-パーサーセクションのこのキーワードは、任意のトークンを示します。 したがって、「テキスト」を{ANY}として説明できます。
2.6 ENDキーワード 、その後に2.2のCOMPILERの後に入力した名前。
3.競合の解決。
文法はアナライザーのタイプと一致する必要があることを理解することが非常に重要です。 この条件に違反すると、ジェネレーターは恐ろしい言葉で誓い始めます。
3.1因数分解。
ルールは同じトークンで始まります。 ジェネレーターはいくつかの選択肢を作成します
例:
S-> a '=' B | '(' C ')'
ご覧のとおり、2つのルールはトークン「a」で始まり、最初のルールLL(1)に違反しています。 S-> a( '=' B | '(' C ')')のように書き換えます
if-elseなどの一部の競合は解決できません。
ステートメント= if '(' Eepr ')'ステートメント[elseステートメント]。
if(a> b)if(a> c)max = a; そうでない場合、max = b;
ここで何を選択するかは明確ではないため、最初の選択肢を選択するという合意がなされました。 この例では、選択は正しいですが、あなたの文法では、そのようなあいまいさを避ける方が良いです。
悪い記録。
S = a [id b] A.
A = id {.id}。
選択するルールは明確ではありません。 [id b]とAは同じトークンで始まります。 この場合、文法を書き直すことをお勧めします。
S = a id(b A | {。Id})。
3.2左再帰。
LL文法の左再帰は決して有効ではありません。 変換によって削除する必要があります。 幸いなことに、左再帰は右に変換できます。
A-> Aa1 | ... | Aan | b1 ... | bm
に
A-> b1B | .. | bmB
B-> a1B | .. | anB | e
RBNFに記録:
A = A b | c。
に
A = c {b}。
3.3意味的な意味。
場合によっては、セマンティクスに基づいてルールが選択されます。 たとえば、次のタイプの式:
Expr = Factor {'+' Factor}。
因子= '(' ident ')'因子| '(' Expr ')' | ident | 数。
つまり このような文法により、次のチェーンが可能になります。
a + b
(a)+ b
(int)a + b
最後のチェーンでは、 '(' ident ')' Factorルールの選択は、識別子のセマンティクスによって決定されます。 つまり タイプとしてidentがある場合、このルールが選択されます。
! 言語の構築に関する非常に失敗した例。 通常、文法では、「キーワード」は別のルールで記述されます。 その後、識別子のチェックは必要ありません。
別の例。
A = ident(。X = 1 ;.){'、' Ident(.x ++;。)} ':' | ident(.Foo();。){'、' ident(.bar();。)} ';'。
この場合、ルールの同じ部分が異なるセマンティックアクションを持っているため、文法を編集できません。 ルールを定義するには、コロンまたはセミコロンの前にあるすべてのトークンを調べる必要があります。 そうしてはじめて、どのルールを選択するかが明らかになります。
解決策:
ブール関数を文法に挿入して、代替を選択できます。
S = a [id b] A.
A = id {.id}。
S = a [IF(isAlias())id b] A.
IsAlias()は、前にある2つのトークンをスキャンする関数です。 bの場合、trueを返します。
トークンt-認識されたトークン
トークンla-次のトークン
t.val-トークン値
t.kind-字句解析器によって決定されるトークンのタイプ
A = IF(FollowedByColon())
ident(。x = 1 ;.){'、' ident(.x ++;。)} ':'
| ident(.Foo();。){'、' ident(。Bar();。)} ';'。
bool FollowedByColon(){ // Token x = la; // while(x.kind==_comma || x.kind== _ident) // x=scanner.Peek(); // true, return x.kind==_colon; }
注:
IFジェネレーターの言語キーワード。
FollowedByColon()関数は、最初のルールを参照します。 彼女が本当なら、それを考えるのは彼です。
トークンのタイプの名前はスキャナーによって割り当てられます。 ただし、TOKENSセクションでそのような発表を行う場合
ident = letter {letter | 桁| 「_」}。
コンマ= '、'。
セミコロン= ';'。
コロン= ':'。
そのスキャナーは、適切な名前の定数を生成します。
const int _EOF = 0;
const int _ident = 1;
const int _comma = 2;
const int _semicolon = 3;
const int _colon = 4;
言語を構築するという観点から見ると、トークンとしての各特殊シンボルの個別の説明は意味がありません。 ただし、先のトークンの検証が必要な条件を記述する必要がある場合は、このような説明が役立つ場合があります。
最初のルールにトークンをチェックする機能があり、2番目のルールにも機能がある場合、スキャナーは元の位置に戻ります。 scanner.ResetPeek()関数を使用して位置をリセットできます。
4.サンプルコード
式を後置記法に変換します。 式は、次の文法で推測する必要があります。
Expr = ident ':=' NumExpr ';'
NumExpr = Term {( '+' | '-')NumExpr}
Term = Multiplier {( '*' | '/')Term}
乗数= ident | 番号| '(' NumExpr ')'
atgファイル:
#include <string> #include <sstream> #include <iostream> #include <fstream> #include <vector> COMPILER expr int toInt(const std::wstring& strbuf) { std::wstringstream converter; int value = 0; converter << strbuf; converter >> value; return value; } std::wstring toString ( int Number ) { std::wostringstream ss; ss << Number; return ss.str(); } IGNORECASE CHARACTERS letter = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz". digit = "0123456789". cr = '\r'. lf = '\n'. tab = '\t'. TOKENS ident = letter {letter | digit | "_"}. number = digit {digit}. COMMENTS FROM "/*" TO "*/" NESTED COMMENTS FROM "//" TO cr lf IGNORE cr + lf + tab PRODUCTIONS expr (.std::wstring str;.) = (.std::wstring s,s1,s2,s3,s4; .) ident (.s1=t->val;.)":=" NumExpr<s2> ";" (. str+=s1; str+=s2; str+=L":=\n";.) {ident(.s3=t->val; s4=L"";.)":=" NumExpr<s4> ";" (. s+=s3; s+=s4; s+=L":=\n"; .)} (. str+=s; std::wofstream outfile ("out.txt", std::ios_base::out); outfile << str << std::endl; outfile.close(); .) . NumExpr<std::wstring &str> = (.std::wstring s1,s2, op; .) Term<s1> (.str+=s1;.) { ("+" (.op=L"+";.)|"-" (.op=L"-";.)) NumExpr<s2> (. str+=s2; str+=op; .) }. Term<std::wstring &str> =(.std::wstring s1,s2, op;.) Multiplier<s1> (.str+=s1;.) { ("*" (.op=L"*";.)|"/"(.op=L"/";.)) Term<s2> (. str+=s2; str+=op; .) }. Multiplier<std::wstring &str> = ident (.str=t->val; .) | number (.str=t->val;.) | (.std::wstring s; .) "(" NumExpr<s> ")" (.str=s;.). END expr.
メイン:
#include <iostream> #include <wchar.h> #include "Parser.h" #include "Scanner.h" #include <string> using namespace std; main(int argc, char *argv[]) { if (argc == 2) { wchar_t *file = coco_string_create(argv[1]); Scanner *scanner = new Scanner(file); Parser *parser = new Parser(scanner); parser->Parse(); delete parser; delete scanner; delete file; return 0; } else { cout << "Use: translator filename" << endl; return 1; } }
作る:
all: translator translator: Coco scanner.o parser.o main.o g++ -o tr.exe scanner.o parser.o main.o main.o: main.cpp g++ -c main.cpp scanner.o: Scanner.cpp Scanner.h g++ -c Scanner.cpp -o scanner.o parser.o: Parser.cpp Parser.h g++ -c Parser.cpp -o parser.o Coco: expr.atg coco expr.atg clean: del Scanner.cpp Scanner.h Parser.cpp Parser.h del Scanner.cpp.old Scanner.h.old Parser.cpp.old Parser.h.old del scanner.o parser.o main.o del translator
文法パーサー関数
void Parser::NumExpr(std::wstring &str) { std::wstring s1,s2, op; Term(s1); str+=s1; while (la->kind == 5 || la->kind == 6) { if (la->kind == 5) { Get(); op=L"+"; } else { Get(); op=L"-"; } NumExpr(s2); str+=s2; str+=op; } }
入り口
a:= b;
a:= a-5;
a:= 9-5 + 2;
a:= 2 + 3 * 4;
a:=(5-4)*(3 + 2);
出る
ab:=
aa5-:=
a952 +-:=
a234 * +:=
a54-32 + *:=