4.6 演算子優先順位解析 (1)演算子(Operation)と演算対象(Operand) 以下、逆ポーランド記法による演算子優先順位解析について述べる。 (従来言語では伝統的に用いられている手法) ① すべて2項演算子とすると <演算対象><演算子><演算対象> の順序となる。 ② 単項演算子を例外処理として扱う。 (前置演算子と後置演算子がある) ③ 単項演算子の例 符号としての+,-(加算、減算の+,-は2項演算子) 論理演算子のNot(C言語の !) C言語の++,--, など ④ 後置演算子と2項演算子は明示的に識別できるものでなくてはならない (重複した文字列の演算子があってはならない。前置演算子は2項演算 子と同一のものがあってもよい) (2)2項演算子と単項演算子解釈の状態遷移 演算対象モードと演算子モードの2状態を持つものとする。 演算子 ( 演算子としての‘(’を含 む) (前置演算子とみなす) 後置演算子 ( 演算子としての‘) ’ , ‘, ’を含む) 2項演算子 S 演算対象モード 演算対象 演算子モード (名前、定数など) ‘(‘ 終了 エラー終了 (“<関数名>(“とみなす) 終了 正常終了 【注】 ‘, ’は、 ①最も優先順位の低い演算子とみなす方法(ただし何も演算しない) ②式の区切りとみなす方法 の二通りがある。 (3)スタックを用いて逆ポーランド記法への変換 ① 演算対象は、逆ポーランドストリームに出力。 ② 演算子は、スタック上の演算子の優先順位と比較 スタック上の演算子の優先順位が高いか等しければポップし、 ポップした演算子を逆ポーランドストリームに出力 現演算子をプッシュ ③ ‘(’は、スタック上にプッシュ。(”<関数名>(“の形式の括弧と式内の括弧 の区別をつける必要がある) ④ ‘)’は、スタック上の‘(’までをポップし、ポップした演算子を逆ポーランド ストリームに出力( 式から括弧が消える)。 ④ ‘,’は、スタック上の‘(’直前までポップし( ‘(’ はスタック上に残る)、 ポップした演算子を逆ポーランドストリームに出力(式から‘,’が消える)。 逆ポーランド記法への変換例(1) 出力ストリーム 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 演算対象 入力ストリーム A スタック A+B*(C/D-E) 逆ポーランド記法への変換例(2) 出力ストリーム 演算子 + A 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 入力ストリーム スタック +B*(C/D-E) 逆ポーランド記法への変換例(3) 出力ストリーム A 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 演算対象 B + スタック 入力ストリーム B*(C/D-E) 逆ポーランド記法への変換例(4) 出力ストリーム 演算子(優先順位が高い) AB 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 * + スタック 入力ストリーム *(C/D-E) 逆ポーランド記法への変換例(5) 出力ストリーム AB 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 前括弧 ( * + スタック 入力ストリーム (C/D-E) 逆ポーランド記法への変換例(6) 出力ストリーム AB 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 演算対象 C ( * + スタック 入力ストリーム C/D-E) 逆ポーランド記法への変換例(7) 出力ストリーム ABC 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 演算子 / ( * + スタック 入力ストリーム /D-E) 逆ポーランド記法への変換例(8) 出力ストリーム ABC 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 演算対象 D / ( * + スタック 入力ストリーム D-E) 逆ポーランド記法への変換例(9) 演算子(優先順位が低い) 出力ストリーム ABCD 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 ①/ ② - 入力ストリーム -E) ①スタックをポップ / ( * + スタック ②現演算子プッシュ 逆ポーランド記法への変換例(10) 演算対象 出力ストリーム ABCD/ 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 E ( * + スタック 入力ストリーム E) 逆ポーランド記法への変換例(11) 出力ストリーム ABCD/E 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 (までをポップ - 入力ストリーム ) ( * + スタック ) 逆ポーランド記法への変換例(12) スタックすべてをポップ 出力ストリーム ABCD/E演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 * + 終了 * + スタック 入力ストリーム 逆ポーランド記法への変換例(13) 元の入力ストリーム 出力ストリーム ABCD/E-*+ 演算子の優先順位例 () 1000(引数用を含 む) ^ 900 +,800(単項演算子) *, /, % 600 +,500(2項演算子) ==,>,… 400(比較演算子) ! 300(Not) |, & 200(論理演算子) = 100 コンマ(,) 10 比較 A+B*(C/D-E) (4)逆ポーランド記法を元に戻す① 逆ポーランド記法 A スタック ABCD/E-*+ 逆ポーランド記法を元に戻す② 逆ポーランド記法 B BCD/E-*+ A スタック 逆ポーランド記法を元に戻す③ 逆ポーランド記法 C CD/E-*+ B A スタック 逆ポーランド記法を元に戻す④ 逆ポーランド記法 D D/E-*+ C B A スタック 逆ポーランド記法を元に戻す⑤ 逆ポーランド記法 / D C B A スタック /E-*+ 逆ポーランド記法を元に戻す⑥ 逆ポーランド記法 E E-*+ (C/D) B A スタック 逆ポーランド記法の実行⑦ 逆ポーランド記法 - E (C/D) B A スタック -*+ 逆ポーランド記法の実行⑧ 逆ポーランド記法 * ((C/D)-E) B A スタック *+ 逆ポーランド記法の実行⑨ 逆ポーランド記法 + (B*((C/D)-E)) A スタック + 逆ポーランド記法の実行⑩ 逆ポーランド記法 (A+(B*((C/D)-E))) スタック (5)制御文の取扱い① go to L2 上記表現は、 ⇒L2 次の実行アドレスをL2ラベルの値にする単項演算とみなす。 制御文の取扱い② If<式>goto L2 上記表現は、 <式>⇒L2 式が成立すれば、次の実行アドレスをL2ラベルの値に 式が成立しなければ、次の実行アドレスは変えない ような2項演算として捉える。 制御文の取扱い③ If<式>Then <文1>Else <文2> 上記表現は、 <式>Then Lelse <式>Then Lelse (Thenを2項演算子、 LeはElse句のラベル) 式が成立しなければ、 <文1> 次の実行アドレスをLeラベルの値に、 Goto Lend 式が成立すれば、 Lelse 次の実行アドレスは変えない <文2> ような2項演算とみなす。 (If<式>goto と逆であることに注意) Lend 制御文の取扱い④ While<式> <文> If<式>Thenと同様 Lstart <式>Then Lend <文> Goto Lstart Lend (6)逆ポーランド記法への変換のための基本処理 【基本的な処理】 ① スタックへのプッシュ ② スタックからのポップアップ ③ 出力ストリームへの出力 【解析用処理】 ④ 優先順位を考慮した現演算子のプッシュ(①、②、③の組合せ) ⑤ 関数呼び出しのための引数のポップアップ ⑥ ‘,’演算子のための引数のポップアップ ⑦ ifブロックやWhileブロックのためのポップアップ ⑧ 1文最後のポップアップ (a) 基本的な処理(C#での表現) ①スタックへのプッシュ public void push(TokenData TK) { Stack[ptrStack++]=TK; } ②スタックからのポップ public TokenData pop() { if(ptrStack==0) return new TokenData("Empty",0,""); ptrStack--; return Stack[ptrStack]; } ③出力ストリームへの出力 public void postFix(TokenData TK) { Polish[numPolish]=TK;numPolish++; } (b)解析用(C#での表現)(その1) ④ 優先順位を考慮したプッシュ public void pushProc(TokenData TK) { if(ptrStack>0) while(ptrStack>0 && TK.priority<=Stack[ptrStack-1].priority && TK.operation!="(") postFix(pop()); push(TK); } 解析用処理(C#での表現)(その2) ⑤ ’(’まで、または関数呼出し最後までをポップ (関数名(…)または 演算子(…)の ‘)’のときの処理) public void popProcS() { TokenData XX=pop(); while(ptrStack>=0) { if(XX.operation=="(") break; if(XX.operation=="Func"){postFix(XX);break; } postFix(XX); XX=pop(); } } 解析用処理(C#での表現)(その3) ⑥ コンマのときの処理(,) 関数呼出しの際の引数の区切り public void popProcF() { TokenData XX = pop(); while(ptrStack>=0) { if(XX.operation=="Func"){push(XX); break;} postFix(XX); XX=pop(); } } 解析用処理(C#での表現)(その4) ⑦ ブロックのときのポップアップ (Ifブロック,Whileブロック) public void popProcBlock() { TokenData XX = pop(); while(ptrStack>=0) { if(XX.operation=="Block")break; postFix(XX); XX=pop(); } } 解析用処理(C#での表現)(その5) ⑧ 文の最後のポップアップ処理 public void popProcE() { TokenData XX; XX=pop(); while(ptrStack>0) { postFix(XX);XX=pop();} if(XX.operation!="Empty")postFix(XX); } (7)第1レベルの構文解析 private void SA0() { numPolish=0; ptrStack=0 ; int i=0;bool Mode=true; while(i<numToken) { if(Mode) { 演算対象モードのときの処理; } else { 演算子モードのときの処理; } i++; } popProcE(); } (A)演算対象モードのときの処理 ① 演算子の +, -, ++, --, ! がきたら単項演算子とみなして 演算子の処理を行う。 ② 演算子の ( がきたら ( をプッシュする。 ③ 名前ifがきたら予約語ifとみなし、 ifを出力ストリームに出力し、Blockをプッシュする。 以降は、if文における論理式とみなす。 ④ 名前と( がきたら、関数呼び出しとみなす。 引数終り符号(ArgEnd)を出力ストリームに出力し、 関数名をプッシュする。 ⑤ 演算子以外なら、出力ストリームに移動し、演算子モードとする。 ⑥ 上記以外はエラー C#によるプログラム例 if(token[i].type=="Delimiter" && (token[i].str=="+" || token[i].str=="-")) pushProc(new TokenData(token[i].str+"$",300,token[i].str)); else if(token[i].type=="Delimiter" && (token[i].str=="++" || token[i].str==" --")) pushProc(new TokenData(token[i].str+"$",300,token[i].str)); else if(token[i].type=="Delimiter" && token[i].str=="!" ) pushProc(new TokenData(token[i].str,40,token[i].str)); else if(token[i].type==“Delimiter” && token[i].str==“(”) pushProc(new TokenData(token[i].str,0,token[i].str)); else if(token[i].type==“Name” && token[i].str==“if”){ postFix(new TokenData("if",0,token[i].str)); push(new TokenData("Block",0,token[i].str)); } else if(token[i].type=="Name" && i<(numToken-1) && (token[i+1].type==“Delimiter” && token[i+1].str==“(”)){ postFix(new TokenData("ArgEnd",0,token[i].str)); push(new TokenData("Func",0,token[i].str)); i++; } // 演算子としての // 括弧 // if文 // 関数呼出し else if(token[i].type!=“Delimiter”) { // 演算対象 postFix(new TokenData(token[i].type,0,token[i].str)); Mode=false;} else{ MessageBox.Show(“001 区切記号の位置の誤り”); break; } (B) 演算子モード ① 次のモードを演算子対象モードとする。 ② 各演算子に対して優先順位を付加して、演算子の処理を行う。 ③ 名前 mod は、演算子として扱う。 ④ カンマ(,)のとき、前述の popProcF の処理を行う。 ⑤ 名前 then, else のとき予約語として扱う。 If文の処理でプッシュされているブロックまでをポップアップし、これらを 出力ストリー に出力し、Blockをプッシュしておく。 ⑥ 名前 endif のとき予約語として扱う。 If文または then, else の処理でプッシュされているブロックまでをポップ アップし、endif を出力ストリームに出力する。(Blockプッシュなし、⑤ と比較) ⑦上記以外のときエラー C#によるプログラム例(その1) Mode=true; if (token[i].type=="Delimiter" && (token[i].str=="+" || token[i].str=="-")) pushProc(new TokenData(token[i].str,100,token[i].str)); else if(token[i].type=="Delimiter" && (token[i].str=="++" || token[i].str==" --")) pushProc(new TokenData(token[i].str,10,token[i].str)); else if(token[i].type=="Delimiter" && (token[i].str=="+=" || token[i].str==" -=")) pushProc(new TokenData(token[i].str,10,token[i].str)); else if(token[i].type=="Delimiter" && (token[i].str=="*=" || token[i].str=="/=")) pushProc(new TokenData(token[i].str,10,token[i].str)); else if(token[i].type=="Delimiter" && token[i].str=="=") pushProc(new TokenData(token[i].str,10,token[i].str)); else if(token[i].type=="Delimiter" && (token[i].str=="||" || token[i].str=="&&" || token[i].str== "%%")) pushProc(new TokenData(token[i].str,30,token[i].str)); else if(token[i].type=="Delimiter" && (token[i].str=="==" || token[i].str=="!=" || token[i].str== ">" || token[i].str==">=" || token[i].str=="=>" || token[i].str== "<" || token[i].str=="<=" || token[i].str=="=<")) { if (token[i].str=="=>") token[i].str=">="; else if(token[i].str=="=<") token[i].str="<="; pushProc(new TokenData(token[i].str,50,token[i].str)); } C#によるプログラム例(その2) else if(token[i].type=="Delimiter" && (token[i].str=="*" || token[i].str=="/"|| token[i].str=="%")) pushProc(new TokenData(token[i].str,200,token[i].str)); else if(token[i].type=="Name" && token[i].str=="mod") pushProc(new TokenData(token[i].str,200,token[i].str)); else if(token[i].type=="Delimiter" && token[i].str=="^") pushProc(new TokenData(token[i].str,400,token[i].str)); else if(token[i].type=="Delimiter" && token[i].str==")") { popProcS(); Mode=false; } else if(token[i].type=="Delimiter" && token[i].str==",")popProcF(); else if(token[i].type=="Name" && (token[i].str=="then" || token[i].str=="else")){ popProcBlock(); postFix(new TokenData(token[i].str,0,token[i].str)); push(new TokenData("Block",0,token[i].str)); } else if(token[i].type=="Name" && token[i].str=="endif"){ popProcBlock(); postFix(new TokenData(token[i].str,0,token[i].str)); } else{ MessageBox.Show(“002 名前の位置または演算子の誤り”); break; } (8)制御構造を含む構文解析 (A) 式解釈の準備 式の部分を逆ポーランド記法で解釈するための領域に移す処理 【例】 ① If <式> then ② For(<式1>;<式2>;<式3>) ③ While <式> ④ Until <式> 以下の2種類がある ・単純に文の終りまで複写 ・何らかの区切り記号/名前を識別して複写 【プログラム例1】 単純なトークン複写 private void token複写(ref int i) // 単純複写 { numToken=0; while(i<numTokenX) {token[numToken]=tokenX[i]; numToken++; i++;} } private void token複写Comma(ref int i) // レベル0のコンマまで { numToken=0;int lebel=0; while(i<numTokenX) { if (tokenX[i].type=="Delimiter" && tokenX[i].str=="(") lebel++; else if(tokenX[i].type=="Delimiter" && tokenX[i].str==")") lebel--; else if(lebel==0 && tokenX[i].type=="Delimiter" && tokenX[i].str==",") return; token[numToken]=tokenX[i]; numToken++; i++; } } 【プログラム例2】 If <式> then private void if論理式(ref int i) { numToken=0; i++; if(i<numTokenX) { while(i<numTokenX) { if(tokenX[i].type=="Name" && tokenX[i].str=="then") { i++;break; } token[numToken++]=tokenX[i++]; } } } 【プログラム例3】 for <式1> ; <式2> ; <式1> ; private void for初期化(ref int i) // 式1 { numToken=0; if(i<numTokenX){ while(i<numTokenX){ if(tokenX[i].type=="Delimiter" && tokenX[i].str==";") { i++;break;} token[numToken++]=tokenX[i++]; } } } 【プログラム例4】 解釈結果の設定 private void SA設定(string op, int pr, string str) { AllStatement[numStatement].operation=op; AllStatement[numStatement].priority=pr; AllStatement[numStatement].str=str; numStatement++; } // 逆ポーランド記法への変換結果の設定 private void 現Polishのオブジェクト設定() { for(int k=0;k<numPolish;k++) AllStatement[numStatement++]=Polish[k]; } 【プログラム例5】 未解決ブロックアドレスのためのプッシュ private void IfPush(int BP, int IfP,int ThenP) { // Ifブロック用 IfStackP++; IfStack[IfStackP].BP=BP; IfStack[IfStackP].IfP=IfP; IfStack[IfStackP].ThenP=ThenP; IfStack[IfStackP].Type="if"; } private void DoPush(int BP, int IfP,int ThenP, string Type) { // 繰り返し用ブロック用 DoStackP++; DoStack[DoStackP].BP=BP; DoStack[DoStackP].IfP=IfP; DoStack[DoStackP].ThenP=ThenP; DoStack[DoStackP].Type=Type; // while, repeat, for numBreak[DoStackP]=0; // ループからの抜け出しの数 } (B) 構文解析の概要 先頭の文字列による判定 private void SA() { int i=0; while(i<numTokenX) { if(tokenX[i].type=="Name" && tokenX[i].str=="if") { if文の処理; } else if(tokenX[i].type=="Name" && tokenX[i].str=="else") { else句の準備; } else if(tokenX[i].type=="Name" && tokenX[i].str=="endif") { endifの処理;} ・ ・ ・ i++; } } If文の場合の解釈 Else や End ifの出現を待ってアドレス解決を行う必要がある If <論理式> Then の式取出し 演算子優先 順位解析 論理式 オブジェクト Then句の処理 飛び先 Then Then句のオブジェクト Elseの処理 この位置を スタックに 保存する 飛び先 Goto Else句の処理 Else句のオブジェクト End Ifの処理 Elseが ない場合 Elseがある場合 分岐先 If文の処理例 if論理式(ref i); // 論理式の設定 numPolish=0; SA0(); // 式の優先順位の解析 numStatementNo++; int oldP=numStatement; SA設定("StNo",0,numStatementNo.ToString()); // 文番号(デバッグ用) 現Polishのオブジェクト設定(); // 式のオブジェクト IfPush(oldP,numStatement,0); // 未解決の飛び先挿入位置プッシュ SA設定("Number",0, ""); // 式の判定 SA設定("then",0,"then"); if(i+1 < numTokenX) MessageBox.Show("thenの後に文は書けません"); 通常のインタプリタではデバッガを用意しており、 文番号の表示を必要とするので, ここでは、StNoというデバッグ用の文番号を出力している。 elseの処理例 IfStack[IfStackP].ThenP=numStatement; SA設定("Number",0,""); SA設定("goto",0,"goto"); numStatementNo++; AllStatement[IfStack[IfStackP].IfP].str=numStatement.ToString(); SA設定("StNo",0,numStatementNo.ToString()); if(i+1 < numTokenX) MessageBox.Show("elseの後に文は書けません"); endIf の処理例 numStatementNo++; SA設定("StNo",0,numStatementNo.ToString()); int IP=IfStack[IfStackP].IfP; if(AllStatement[IP].str== "") // Elseがない場合 AllStatement[IP].str=numStatement.ToString(); else // Elseがあった場合 AllStatement[IfStack[IfStackP].ThenP].str=numStatement.ToString(); if(i+1 < numTokenX) MessageBox.Show("endifの後に文は書けません"); IfStackP--;
© Copyright 2024 ExpyDoc