2012-02-17 11 views
2

私の質問は基本的に「何がYACC /バイソンに良いスタイルを構成している?」されます関連して、私がバイソンにそれが良いことをさせるかどうかを問わず。Bisonスタイル:自分のスタックが悪いですか?グローバルは悪いですか?

たとえば、私のBisonプログラムは、元のコードよりもグローバルに大きな影響を受けています。次のことを考えてみましょう:

prog : 
     vents{ /*handle semantics...*/ } 
     unity{ /*handle semantics...*/ } 
     defs; 

私は「通気口」と「統一」後2中括弧で区切られたブロック間の情報を渡したい場合は、私はグローバル変数(技術的には、ファイルレベルの変数を使用していることを考えますスコープと内部リンケージ)は、私が情報隠蔽の観点からできることです。私がこれらのブロック内で宣言した変数は、そのブロックのローカルなものです(私は思っています...)。そして、C++宣言を置くことができる他の指定された場所はファイルレベルのスコープになります。

"yyparse()"関数に変数宣言を挿入することができれば、これは私のニーズに適しています。このようなコードや、そのような変数を注入するためのフックがありますか?または、グローバルはBisonを使用するには受け入れられる部分ですか?

このように、これらのセクション間で情報を渡したいとは思わないかもしれません。しかし、ちょうど$$、$ 1、$ 2などを使って周りをすべて渡すことは、私にとっては難しいようです。私はちょうどそれを "持っていない"ですか?

残りの部分を受け入れる場合でも、私のグローバル変数の1つが特に疑わしいと感じます。これはstd :: stack型であり、入力言語の条件文のサポートに関連しています。

コンパイラの入力に条件( "if/else")が発生すると、結果としてシーケンスからプルされた数字が続くテキスト文字列で構成される3つのアセンブリ言語ラベルが出力されます。

私は最初に "if"に出会ったときにシーケンス番号を取得しています(if構造体がネストされているのでスタックにプッシュしてから、後で "peeks"や "pops" ")、必要なラベルやジャンプを作成します。例えば、私の状態の後、私の" if "ブロック、そして" else "ブロックです。

代わりに$ -2のようなものを使ってこの作業をしようとしましたが、この識別子は私の条件の始めではなく、ちょうどコンパイルされたブロックの終わりに関係していました。 $で抽象化されたシステムは、コードが左から右に読み込まれているように見えますが、その中の構造がどのようにネストされているかについての概念はありません。

私はあなたのすべてが私のためにこの仕事をすることを期待していませんが、私は$$、$ 1、$ -1などを使用しようとすると、少なくとも正しい道のりでしたか?それは私があまりにもすぐにあきらめ、および/または私はすなわち完全に私の古いアドホックコードを投げから、きれいなシートアプローチを取ってから利益を得るであろうことはかなり可能です。

その場合ですか?または、スタックとそのグローバルで正常にスタックされたアプローチですか?

答えて

2

私はグローバル変数を使用して回避することの難しさを見ていない、私はやっとエラーや同様のことを知らせるためにそれらを使用しています。

は、それが何を生成する必要があり、パーサを考えてみて?それが作られてどのように抽象構文木..

?これは、すべてのノードにいくつかの情報と子だけが含まれているため、グローバル変数は必要ありません。

私はあなたに私が書いている言語の覗き見をあげるだけであなたのアイデア与えるために:

bexp: 
    bexp T_PLUS bexp { $$ = new ASTBExp($2,$1,$3); } 
    | bexp T_MINUS bexp { $$ = new ASTBExp($2,$1,$3); } 
    | bexp T_TIMES bexp { $$ = new ASTBExp($2,$1,$3); } 
    | bexp T_DIV bexp { $$ = new ASTBExp($2,$1,$3); } 

uexp: 
    raw_value { $$ = $1; } 
    | UOP_NOT uexp { $$ = new ASTUExp($1,$2); } 
    | T_LPAREN bexp T_LPAREN { $$ = $2; } 
    | var_ref { $$ = new ASTVarRef((ASTIdentifier*)$1); } 
    | call { $$ = $1; } 

をあなたはしている文法のchildred、でインスタンス化され解析されたすべてのノードを見ることができるようにまた、抽象構文木の意味的に子供とルート要素は、私はちょうど取得した

start: root { Compiler::instance()->setAST((ASTRoot*)$1); } 
; 

root: 
    function_list { $$ = new ASTRoot($1); } 
; 

のようなものである$$

で返さ私のCompilerクラスのインスタンスに渡してください。あなたはyyparse()

bool parseSource() 
{ 
    //yydebug = 1; 
    freopen(fileName, "r", stdin); 
    yyparse(); 

    return !failed; 
} 

を呼び出す関数を見れば

は今、私はちょうどファイルを開き、構文解析ルーチンを呼び出します。この関数は、ここでCompilerクラスによって呼び出されます:あなたは解析ルーチンが呼び出され、ここで見ることができるように

bool compile() 
    { 
    if (!parseSource()) 
     return false; 

    if (!populateFunctionsTable()) 
     return false; 

    ast->recursivePrint(0); 
    Utils::switchStdout(binaryFile); 
    ast->generateASM(); 
    Utils::revertStdout(); 

    assemble(); 

    return true; 
    } 

、ルーチンは、ツリー全体を作成し、Compilerクラス内に設定します。木の再帰的訪問(機能generateASM)は、汚い仕事をします。

パーザをどのように使うべきかを少し明確にしたいと思ったら、さらに詳しい情報が必要かどうか教えてください。パーサーですべての作業をする必要はありません。その部分を解析するだけで、抽象構文木を使っていくつかの再帰呼び出しを行うことができます。

別の実用的な例は、文法では、単純に作業する、構築する場合/他の管理するために、特別なノードが作成され

if_stat: 
    KW_IF T_LPAREN exp T_RPAREN block %prec LOWER_THAN_ELSE { $$ = new ASTIfStat($3, $5); } 
    | KW_IF T_LPAREN exp T_RPAREN block KW_ELSE block { $$ = new ASTIfStat($3, $5, $7); } 
; 

として定義されて、話をしている場合/ else文であります

void generateASM() 
    { 
    if (m_fbody == NULL) 
    { 
     m_condition->generateASM(); 
     printf("NOT\n"); 
     printf("JUMPC iflabel%u\n", labelCounter); 
     m_tbody->generateASM(); 
     printf("iflabel%u:\r\n", labelCounter); 

     ++labelCounter; 
    } 
    else 
    { 
     u32 c = labelCounter++; 
     u32 d = labelCounter++; 

     m_condition->generateASM(); 
     printf("JUMPC iflabel%u\n", c); 
     m_fbody->generateASM(); 
     printf("JUMP iflabel%u\n", d); 
     printf("iflabel%u:\n", c); 
     m_tbody->generateASM(); 
     printf("iflabel%u:\n", d); 
    } 
    } 
+0

ありがとうございます。あなたの記事を読んで、私は自分のプログラムを改善する方法を間違いなく見ている。要約すると、それは – user1172763

+0

のように見えます。あなたの文法のその他の要素を表すクラスのライブラリを持っているのは、あなたが望むだけ多くのことをすることができるからです。それらを型チェックしたり、ツリーを剪定したり、構造を最適化したり、ASMコードを再帰的に訪問するだけで生成することができます。 – Jack

0

中間レベルのルールには、スタックに値をプッシュすることができます。あなたは

rule 
    : A B { ... } C 

をお持ちの場合は

Bisonは自動的

some_identifier 
    : /* empty */ { ... } 

rule 
    : A B some_identifier C 

にこれを変換し、その値が正確なようにアクセスすることができます。この場合、中規模セマンティックアクションはBisonスタックに格納された値を持ち、後で同じルールで再びアクセスされます。

通常、これらの関数は再帰的です。単純な次のスニペットを考えてみましょう

// C++ 
class Statement { public: virtual ~Statement() {} }; 
class Expression : public Statement {}; 
class IfStatement : public Statement { Statement* if_true; Expression* condition; } 

// Bison 
%type if_statement if_stmt 
%type statement stmt 
%union { 
    IfStatement* if_stmt; 
    Statement* stmt; 
} 

if_statement 
    : if { $$ = new IfStatement(); } 
     '(' expression { $2->condition = $4; } 
     ')' statement { $2->if_true = $7; $$ = $2; } 

statement 
    : if_statement { $$ = $1; } 
    | ... 

このような再帰的な機能を実行する外部スタックは必要ありません。

0

%parse-param directiveを使用して渡す追加データを宣言できます。余分なデータを少し隠すことができますが、解析関数に渡す必要があります。

関連する問題