一個C語言宣告解析器的設計與實現

誰不小心的發表於2013-10-09
概論:C語言的宣告解析往往復雜多樣,使得初學者不知所云,例如double (*(*(*fp3)())[10])() 定義的是什麼?int *a[10]和int (*a)[10]有什麼區別?C語言的宣告解析遵循什麼規則?本文主要為你介紹C語言宣告解析的規則,然後為你介紹編譯器的一個與之相關的部分——宣告解析器的設計與實現。

本文來源:一個C語言宣告解析器的設計與實現


一、一些複雜宣告例項


int *a[10]
int (*a)[10]
int (*(*func)[5][6])[7][8];
int (*(*func[7][8][9])(int*))[5];
看看這些宣告自己能否很好的解析:
1)a是一個陣列,陣列元素是一個指向int的指標
2)a是一個指標,指向具有10個int的陣列
3)func是一個指標,它指向一個5X6的陣列,陣列元素是指標,這些指標指向7X8的int陣列
4)func是陣列,陣列元素是一個指標,這個指標指向一個行參為(int *)的函式指標,這個指標指向int[5]的陣列


二、複雜宣告解析規則


關於C語言的宣告解析規則,可以參考這裡


需要說明的是,合法的宣告中存在限制條件,你可以這樣做
int (*fun())():函式的返回值可以是一個函式指標
int (*foo())[]:函式的返回值可以是一個指向陣列的指標
int (*foo[])():陣列中可以是函式指標
int foo[][]:陣列裡可以有陣列
你不可以這樣做
foo()():函式的返回值不能是一個函式
foo()[]:函式的返回值不能是一個陣列
foo[]():陣列裡面不能是函式
也就是說:函式和函式不相容,函式與陣列互不相容。


三、一個C語言宣告解析器的設計與實現



設計方案:主要資料結構是一個棧,我們從左向右讀取,直到遇到識別符號為止。然後繼續向右讀取一個標記,如果遇到括號就向左讀取和處理。為了簡化程式設計,說明原理,我們這裡約定函式不含有引數。另外,我們只解析正確的宣告,對錯誤宣告的出錯處理不是很完善。 


主要的資料結構:
struct token{char type;

char string[MAXTOKENLEN];};


//第一個標識之前的所有標識
struct token stack[MAXTOKENS];


//正在處理的那個標識
struct token this;


功能程式:
//字串分類
classify_string()
             檢視this,通過string成員,確定type成員的值:“type”,qualifier或者是identifier
gettoken():取標記
            讀取下一個標記到this.string
            如果是數字和字母組合,利用clasify_string分類;
            如果是一個單字元,this的type值定為該字元;注意使用nul結尾

read_to_first_identifier():

            讀到第一個識別符號號

gettoken()

            入棧,直到遇到第一個識別符號



解析程式:
deal_with_function():
               當讀取到)以後,列印“函式返回”
deal_with_array():
                讀取到】,然後列印size
deal_with_pointer():
                 解析*和左側的(
go_left():
                 不斷解析棧頂,直到它變成空
deal_with_declaration()
                 如果遇到最後宣告結尾(gettoken==0),go_left
                 switch this.type ‘[’ :deal_with_array()
                                            '(': deal with funciton()
                                            ‘)’:deal with pointer()

                  deal with declaration()//遞迴


主程式:
main
init_parse_decl
read_to_first_identifier
deal_with_declarator


具體的程式碼:

#include<stdio.h>
#include<ctype.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#define MAXTOKENLEN 64
#define MAXTOKENS 100
#define MAXDECLLEN 200

struct token{
  char type;
  char string[MAXTOKENLEN];
};
struct decl{
  char string[MAXDECLLEN];
  int current_location;
};

struct decl mydecl;
int top=-1;
struct token this;//the current token
struct token stack[MAXTOKENS];//the tokens before the first identifer
#define IDENTIFIER 'I'
#define QUALIFIER 'Q'
#define TYPE 'T'
#define pop stack[top--]
#define push(s) stack[++top]=s
//#define debug 
char classify_string(const char *string);

int main(int argc, char *argv[])
{
  init_parse_decl();
  read_to_first_identifier();
  deal_with_declaration();
  printf("\n");
    return 0;
}

/*
 * init the value of mydecl*/
int init_parse_decl(){
  if( fgets(mydecl.string,MAXDECLLEN,stdin) == NULL){
    perror("can not read from the stdin");
  }
  if(strlen(mydecl.string)==MAXDECLLEN-1)
    printf("you input is too long, you may get an error!");
  mydecl.current_location=0;

  #ifdef debug
  printf("init:we get last char of mydecl:%c,%d\n",mydecl.string[strlen(mydecl.string)],mydecl.string[strlen(mydecl.string)]);
  #endif
}


/*
  get a token from the current string,value the "this" and move the pointer
notice: we may get a '\0' at the end of the string
  */
int gettoken(){
  char *ch_pointer=this.string;
  //leave out the blank
  while(mydecl.string[mydecl.current_location]==' '){
    mydecl.current_location++;
  }

  *ch_pointer=mydecl.string[mydecl.current_location];
  if(isalnum(*ch_pointer)){
  	while(isalnum(*ch_pointer)){
	  mydecl.current_location++;
	  ch_pointer++;
	  *ch_pointer=mydecl.string[mydecl.current_location];
	}

	*ch_pointer='\0';
	this.type=classify_string(this.string );//indentifier,qualifier,type
	#ifdef debug 
	printf("we get token:%s ",this.string );
	#endif
	return 1;
  }

  //else:(, ), [, ] 
  
//  printf("current location is:%d\n",mydecl.current_location);
 // printf("(%d)",mydecl.string[mydecl.current_location]);
  switch(*ch_pointer){
    case '*':
    case '(':
    case ')':
    case '[':
    case ']':
	      this.type=*ch_pointer;
	      this.string[1]='\0';
	      mydecl.current_location++;
	      #ifdef debug 
	      printf("we get token:%s ",this.string );
	      #endif
	      break;
    case '\n':
    case '\0':this.type='\0'; 
	     // printf("we come to the end\n");
	      strcpy(this.string,"then end");
	      return 0;

   default :
	      printf("we can not read this indentifier %d\n",*ch_pointer);
	      parse_error();
  }
  return 1;
}

char classify_string(const char *string){
  if(isspace(*string))
    return '\0';
  if(!strcmp(string,"const")) 
   return QUALIFIER;
  if(!strcmp(string,"volatile")) 
   return QUALIFIER;
  

  if(!strcmp(string,"void")) 
    return TYPE;
  if(!strcmp(string,"char")) 
    return TYPE;
  if(!strcmp(string,"signed")) 
    return TYPE;
  if(!strcmp(string,"unsigned")) 
    return TYPE;
  if(!strcmp(string,"short")) 
    return TYPE;
  if(!strcmp(string,"int")) 
    return TYPE;
  if(!strcmp(string,"long")) 
    return TYPE;
  if(!strcmp(string,"float")) 
    return TYPE;
  if(!strcmp(string,"struct")) 
    return TYPE;
  if(!strcmp(string,"union")) 
    return TYPE;
  if(!strcmp(string,"enum")) 
    return TYPE;

  return IDENTIFIER ;
}

/*follow the dec string until the first identifier,push token to a stack*/
int read_to_first_identifier(const char *mydecl){
  if(gettoken()==0){
    printf("readtofirst:missing the identifier,please put in your string...\n");
    return 0;
  }
  //the token is a struct with member type and string
  while(this.type!=IDENTIFIER)
  {
    push(this);//a stack with element of token
    if(gettoken()==0){
      printf("readtofirst:missing the identifier\n");
      return 0;
    }
  }
  printf("%s is ", this.string);
  
  return 1;
}



/*
deal with the next token in diff ways according to the token type
we just go to right
1) "(":deal in a function way,deal_with_declaration,
2) "[":deal in a array way,deal_with_declaration,
3) ")":deal with a pointer way,deal_with_declaration
4) NULL:move_left,
*/
int  deal_with_declaration()
{
  if(gettoken()==0){
    move_left();
    return 1;
   }  
  //
  switch(this.type){
    case '(': deal_with_function();break;
    case ')': deal_with_pointer();break;
    case '[': deal_with_array();break;
    default : printf("there should not be a %c after the identifier\n",this.type );parse_error();
  }
  deal_with_declaration();
}


/*the current token is [,the content in the '[]' may be digtal,al,and other*/
int deal_with_array(){
  printf("array of ");
  while(this.type!=']'){
    gettoken();//we may get an "]" or digital
    if(isdigit(this.string[0])){
      printf("%d  ", atoi(this.string));
      //the next token must be ]
      gettoken();
      if(this.type!=']'){
	printf("the array miss the ']'before %s",this.string);
	parse_error();
      }
    }
  }
  return 0;
}

int parse_error(){
	printf("press any key to exit\n");
	getchar();
	exit(0);
}

/*the current token is ')' */
int deal_with_pointer(){
  while(stack[top].type=='*'){
    printf("pointer pointing to \n");
    pop;
  }
  if(stack[top].type!='('){
    printf(" missing an '(' after %s\n",stack[top].string );
    parse_error();
  }
  else{
    pop;
    return 1;

  }
}

/*the current token is '('*/
int deal_with_function(){

  while(this.type!=')'){
    gettoken();
    if(this.type=='\0'){
      printf(" missing an ')' before %s",this.string);
      parse_error();
    }
  }
  printf(" function returning ");
  return 1;
}

int move_left(){
  while(top>=0){
    switch (stack[top].type){
      case '*': printf(" pointer pointing to ");break;
      case  QUALIFIER: printf(" %s ",stack[top].string );break;
      case  TYPE: printf(" %s ",stack[top].string );break;
      default :printf("there is someting wrong about %s",stack[top].string);parse_error();break;
    }
    top--;
  }
}


外部參考:[1]《C專家程式設計》第三章


相關文章