LoginSignup
1
0

シンプルな再帰下降のサンプル(2)

Last updated at Posted at 2019-05-27

力弱く ( ) に対応

前回は三種類のオペレータと数字1桁だけに対応したパーサを掲載し、オペレータの優先順位を解決する方法を示しました。
シンプルな再帰下降のサンプル
それに優先順位を変更する'('と')'への対応を追加します。
あと / の対応とホワイトスペースの読み飛ばしも。

'('に出会ったらexpr()を呼び出します。')'も含めて対応していない文字に出会ったらexpr()から抜けてきます。
これで再帰下降っぽくなりました。

t2.c
/*
再帰下降サンプル
cc -o t2 t2.c
*/

#include <stdio.h>

char* text="2*(3+4)*5" ;
char* ptr ;
char token ;

//	計算結果を積むスタック
int stack[32] ;
int sp=0 ;

void push(int value){
	stack[sp]=value ;
	sp++ ;
}

int pop(void){
	sp-- ;
	return(stack[sp]) ;
}


void get_token(void){
	while(*ptr==' ' || *ptr=='\t') ptr++ ;

	token=*ptr ;
	
	if(*ptr!=0){
		ptr++ ;
	}
}



void expr(void);

//	
void factor(void){
	get_token() ;
	if(token>='0' && token<='9'){
		push(token-'0') ;
		printf("number(%d)\n", token-'0') ;
		get_token() ;
	}
	else if(token=='('){
		expr() ;
		get_token() ;
	}
}

//	掛け算	
void mul(void){
	factor() ;
	while(1){
		if(token=='*'){
			factor() ;
			printf("mul\n") ;
			push(pop() * pop()) ;
		}
		else if(token=='/'){
			factor() ;
			printf("div\n") ;
			int top=pop() ;
			push(pop() / top) ;
		}
		else{
			break ;
		}
	}
}

//	足し算と引き算
void add(void){
	mul() ;
	while(1){
		if(token=='+'){
			mul() ;
			printf("plus\n") ;
			push(pop() + pop()) ;
		}
		else if(token=='-'){
			mul() ;
			printf("minus\n") ;
			int top=pop() ;
			push(pop() - top) ;
		}
		else{
			break ;
		}
	}
}

//	式の評価
void expr(void){
	add() ;
}

int main(void){
	ptr=text ;
	expr() ;

	printf("answer=%d\n", pop()) ;

	return 0 ;
}

解説

このパーサは数字一文字と四則演算のオペレータ + - * / それから ( を受け付け、算数の優先順位の通りに計算して結果を出力します。構文木は生成しません。

以下、+ ,* , ( について解説します。+と-、*と/は優先順位が同じですので、それぞれの後者は省略します。

main()からexpr()を呼んだ時にget_token()のptrは先頭の2を指しています(char text="2*(3+4)*5"の先頭の2です)。
add()の中ではまずmul()を呼び出します。これは+は expr+expr の形、たとえば 3+4 のような形をしているはずだから、まず+より左側を処理する必要があるからです。
同様にmul()も expr * expr の形をしているはずなので、まず先にfactor()を呼び出します。factor()は * よりも高い優先順位を持つモノの解析をします。
factor()は、数字が出てきたら数値をpush()して抜けます。また、開き括弧 ( が出てきたら、再帰的にexpr()を呼び出します。それ以外は単に関数を抜けてきます。

さて、最初の状態

2*(3+4)*5
^
でexpr()を呼ぶとexpr()→add()→mul()→factor()と呼ばれ、factor()がget_token()を呼び出したら'2'が返って来ます。factor()は2をpush()して抜けます。
戻ってきた所はmul()のwhile()です1。ここまでで * より左側にあって優先順位の高いものは、すべて数値になってpush()されているはずですから、* が出てきたら掛け算をしたいです。ですがまだ * の後ろの式が数値になっていません。そこで

2*(3+4)*5
 ^

ここから後ろを数値に直すのに、もう一度factor()を呼びます。
factor()は'('が出てきたのでexpr()を呼び出します。そうすると()で囲まれた中が数値になってpush()されて帰って来ます。

mul()まで戻ってきた時には、2と(3+4)はそれぞれ数値になってpush()されていますから、それを取り出して掛け算をして、それをまたpush()します。

これでmain()に戻ってきた時には、結果がpush()されているはずです。

add()の中でmul()が2回以上呼ばれるのも、+,-の前後の式の優先順位の高い部分を数値になおすためで、mul()と同様です。
while()は、2+3+4+5 のような場合に対応する繰り返しです。

複数桁の数字への対応

複数桁の数字への対応はfactor()で行う方法も考えられますが、「数は'0'~'9'の1回以上の繰り返し」と定義すると、オペレータの優先順位とは関係なく解釈できます。こういう場合はget_token()の中で処理をして、「数値だよ」というステータスを付けて数値を返す方法もあります。
C言語の 0x00ff のような数も 1.2e-34 のような数も、factor()の中では数値として一律に扱えるので便利です。
複数桁の数字への対応のような処理は字句解析と言い、構文解析と切り離して処理するのが一般的です。


最後に シンプルな再帰下降のサンプル(3)変数と代入で変数と代入に対応して一回りということで。

  1. ループを使うのは左再帰の解決法の1つです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0