単項演算子も使えるようにする
これまでは負の数を指定する方法がありませんでした
単項演算子をサポートすることで負の数も使えるようにしてみましょう
仕組み
単項演算子も使えるようにしたパーサが処理すべき式は必ず次のような形式になっています
完結式 ::= 部分式_comma
部分式_comma ::= 部分式_add_sub 部分式_comma_
部分式_comma_ ::= 演算子_comma 部分式_add_sub 部分式_comma_
演算子_comma ::= ,
部分式_add_sub ::= 部分式_mul_div_mod 部分式_add_sub_
部分式_add_sub_ ::= 演算子_add_sub 部分式_mul_div_mod 部分式_add_sub
演算子_add_sub ::= + あるいは −
部分式_mul_div_mod ::= 部分式_unary 部分式_mul_div_mod_
部分式_mul_div_mod_ ::= 演算子_mul_div_mod 部分式_unary 部分式_mul_div_mod_
演算子_mul_div_mod ::= * あるいは / あるいは %
部分式_unary ::= 演算子_unary 定数式
演算子_unary ::= + あるいは − あるいは ~ あるいは !
定数式 ::= 数値 あるいは ( 部分式_comma )
数値 ::= 16進数 あるいは 8進数 あるいは 10進数
16進数 ::= 0x[0-9a-fA-F]+
8進数 ::= 0[0-7]*
10進数 ::= [1-9][0-9]*
今追加しようとしている「単項演算子」は既に実装してある「掛け算・割り算」よりも優先順位が高いので「掛け算・割り算」と「定数式」との評価との間に「単項演算子」の処理を割り込ませます
簡単ですね
では実装してみましょう
実装
次のメソッドを追加しましょう
class string_calc : protected istringstream<char> { . . . bool operator_unary_prefix( any_value_t* presult ); . . . }; bool string_calc::operator_unary_prefix( any_value_t* presult ){ bool success = true; if( !skipspace()){ error_unexpected_eof(); success = false; } else{ char c = getch(); if(( c == '+' )||( c == '-' )||( c == '!' )||( c == '~' )){ success = operator_unary_prefix( presult ); if( success && presult ){ if( c == '+' ){ *presult = presult->operator_unary_plus(); } else if( c == '-' ){ *presult = presult->operator_unary_minus(); } else if( c == '~' ){ *presult = presult->operator_unary_logical_not(); } else if( c == '!' ){ *presult = presult->operator_unary_conditional_not(); } } } else{ ungetch( c ); success = constant( presult ); } } return success; }
any_value_t に次のメソッドを追加しましょう
unsigned long operator_typecast_to_ulong() const { const char* p = &m_value[0]; char* endp = const_cast<char*>(p); unsigned long result = strtoul( p, &endp, 0 ); return result; } any_value_t operator_unary_plus() const { any_value_t result = *this; return result.assign( TYPEOF_INTEGER, stringprintf( "%d", +result.operator_typecast_to_long())); } any_value_t operator_unary_minus() const { any_value_t result = *this; return result.assign( TYPEOF_INTEGER, stringprintf( "%d", -result.operator_typecast_to_long())); } any_value_t operator_unary_conditional_not() const { any_value_t result = *this; return result.assign( TYPEOF_INTEGER, !operator_typecast_to_bool() ? "1" : "0" ); } any_value_t operator_unary_logical_not() const { any_value_t result = *this; return result.assign( TYPEOF_INTEGER, stringprintf( "%u", ~result.operator_typecast_to_ulong())); }
実装の最適化
「単項演算子」の右側は「数値」が出てくるまでは必ず「単項演算子」しか出てこないはずなので、ここを再帰的にすることは少し無駄です
下記のようにループに展開してしまってもいいでしょう
bool string_calc::operator_unary_prefix( any_value_t* presult ){ bool success = true; std::vector<char> prefix_unary_operators; while( skipspace()){ char c = at( 0, SEEK_CUR ); if(( c == '+' )||( c == '-' )||( c == '!' )||( c == '~' )){ prefix_unary_operators.push_back( getch()); } else{ break; } } if( !skipspace()){ error_unexpected_eof(); success = false; } else{ success = constant( presult ); if( success && presult ){ // 単項演算子は評価順が右→左なので reverse_iterator であることに注意 for( std::vector<char>::reverse_iterator p = prefix_unary_operators.rbegin(); p != prefix_unary_operators.rend(); ++p ){ if( *p == '+' ){ *presult = presult->operator_unary_plus(); } else if( *p == '-' ){ *presult = presult->operator_unary_minus(); } else if( *p == '~' ){ *presult = presult->operator_unary_logical_not(); } else if( *p == '!' ){ *presult = presult->operator_unary_conditional_not(); } } } } return success; }
この最適化は実は他の段階にも言えることで、再帰を適切なループに展開することによって実行時の負荷が軽減されます