単項演算子も使えるようにする

これまでは負の数を指定する方法がありませんでした
単項演算子をサポートすることで負の数も使えるようにしてみましょう

仕組み

単項演算子も使えるようにしたパーサが処理すべき式は必ず次のような形式になっています


完結式 ::= 部分式_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-9​a-f​A-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;
}

この最適化は実は他の段階にも言えることで、再帰を適切なループに展開することによって実行時の負荷が軽減されます