Post

Add a tokenizer to allow space characters between tokens

这节有tokenize写法的雏形(典型的consume、expect、eof标记和空白忽略)。具有两次遍历,一次遍历忽略空白字符串生成token,第二次遍历token链表对加减表达式求和。

知识点

  • calloc(size_t nitems, size_t size)

这个函数声明在stdlib.h,记得malloc但没用过calloc.

malloc和calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能.

  • va_*。声明在stdarg.h用来访问函数的可变参数。使用步骤
    • 定义一个可变参数函数,至少要有一个固定参数(即 last 参数)。
    • 声明一个 va_list 变量,用于存储可变参数列表的信息。
    • 使用 va_start 宏初始化 va_list 变量。
    • 使用 va_arg 宏依次获取每个可变参数。
    • 使用 va_end 宏清理 va_list 变量。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
      void total(int count,...){
          va_list va;
          va_start(va,count);
          int result=0;
          for(int i=0;i<count;i++){
              result+=va_arg(va,int);
          }
          va_end(va);
          printf("result %d",result);
      }
    
  • isspace

判断是否空格字符串。习惯了手写居然不知道ctype.h还有这个函数

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef enum{
    TK_RESERVED,//Keywords or punctuators
    TK_NUM,//intege literals
    TK_EOF,//end of file
}TokenKind;

typedef struct Token Token;
struct Token{
    TokenKind kind;
    Token *next;    
    long val;       //if kind is TK_NUM,its value
    char *str;      //Token string
};
Token *token;//current token

//reports an error and exit.
void error(char *fmt,...){
    va_list ap;
    va_start(ap,fmt);
    vfprintf(stderr,fmt,ap);
    fprintf(stderr,"\n");
    va_end(ap);
    exit(1);
}

// consumes the current token if it matches `op`
bool consume(char op){
    if(token->kind!=TK_RESERVED||token->str[0]!=op)
        return false;
    token=token->next;
    return true;
}

//ensure that the current token is `op`
void expect(char op){
    if(token->kind!=TK_RESERVED||token->str[0]!=op)
        error("expected '%c'",op);
    token=token->next;
}

//ensure that the current token is TK_NUM.
long expect_number(void){
    if(token->kind!=TK_NUM)
        error("expected a number");
    long val=token->val;
    token=token->next;
    return val;
}

bool at_eof(void){
    return token->kind==TK_EOF;
}

//create a new token and add it as the next token of `cur`
Token *new_token(TokenKind kind,Token *cur,char *str){
    Token *tok=calloc(1,sizeof(Token));
    tok->kind=kind;
    tok->str=str;
    cur->next=tok;
    return tok;
}

//Tokenize `p` and returns new tokens
Token *tokenize(char *p){
    Token head={};
    Token *cur=&head;
    while(*p){
        if(isspace(*p)){
            p++;
            continue;
        }

        if(*p=='+'||*p=='-'){
            cur=new_token(TK_RESERVED,cur,p++);//直接指针指到指定的位置,比较的时候所以只比较token->str[0]
            continue;
        }

        if(isdigit(*p)){
            cur=new_token(TK_NUM,cur,p);
            cur->val=strtol(p,&p,10);
            continue;
        }
        error("invalid token");
    }
    new_token(TK_EOF,cur,p);
    return head.next;//头是空的直接指向下一个
}

int main(int argc,char **argv){
    if(argc!=2){
        fprintf(stderr,"%s:invlid number of arguments\n",argv[0]);
        return 1;
    }
    token= tokenize(argv[1]);
    printf(".intel_syntax noprefix\n");
    printf(".global _main\n");
    printf("_main:\n");
    printf("    mov rax, %ld\n",expect_number());
    while(!at_eof()){
        if(consume('+')){
            printf("    add rax, %ld\n",expect_number());
            continue;
        }
        expect('-');
        printf("    sub rax, %ld\n",expect_number());
    }
    printf("  ret\n");
    return 0;
}
This post is licensed under CC BY 4.0 by the author.