[コンパイラ]トークナイザー

文字列をトークン列に分割することをトークナイズするという

#include <ctype.h> // 文字の種類の判定や文字の変換
#include <stdarg.h> // 可変長引数
#include <stdbool.h> // bool, true, false
#include <stdio.h>
#include <stdlib.h> // strtol
#include <string.h>

typedef enum {
	TK_RESERVED, // 記号
	TK_NUM, // 整数トークン
	TK_EOF // 入力の終わりを表すトークン
} TokenKind;

typedef struct Token Token;

struck Token {
	TokenKind kind; // トークンの型
	Token *next; // 次の入力トークン
	int val; // kindがTK_NUMの場合、その数値
	char *str; // トークン文字列
};

// 現在着目しているトークン
Token *token;

// エラーを報告する為の関数
// printfと同じ引数
void error(char *fmt, ...){
	va_list ap;
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n");
	exit(1);
}

// 次のトークが期待している記号の時には、トークンを1つ読み進めて真を返す、それ以外の場合には偽を返す
bool consume(char op){
	if(token->kind != TK_RESERVED || token->str[0] != op)
		return false;
	token = token->next;
	return true;
}

// 次のトークが期待している記号の時には、トークンを1つ読み進めて真を返す、それ以外の場合にはエラーを返す
void expect(char op){
	if(token->kind != TK_RESERVED || token->str[0] != op)
		error("'%c'ではありません", op);
	token = token->next;
}

// 次のトークが数字の場合、トークンを1つ読み進めて真を返す、それ以外の場合にはエラーを返す
int expect_number(){
	if(token->kind != TK_NUM)
		error("数ではありません");
	int val = token->val;
	token = token->ext;
	return val;
}

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

// 新しいトークンを作成してcurに繋げる
Token *new_token(TokenKind, Token *cur, char *str){
	Token *tok = calloc(1, sizeof(Token));
	tok->kind = kind;
	tok->str = str;
	cur->next = tok;
	return tok;
}

// 入力文字列pをトークナイズしてそれを返す
Token *tokenize(char *p){
	Token head;
	head.next = NULL;
	Token *cur = &head;

	while(*p){
		if(isspace(*p)){
			p++;
			continue;
		}

		if(*p == '+' || *p == '-'){
			cur = new_token(TK_RESERVED, cur, p++);
			continue;
		}

		if(isdigit(*p)){
			cur = new_token(TK_NUM, cur, p);
			cur->val = strtol(p, &p, 10);
			continue;
		}

		error("トークナイズできません");
	}

	new_token(TK_EOF, cur, p);
	return head.next;
}


int main(int argc, char **argv){
	if(argc != 2){
		fprintf(stderr, "引数の個数が正しくありません\n");
		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()); // ldはlong d, strtolは文字列をlongに変換

	while(!at_eof()){
		if(consume('+')){
			printf("	add rax, %d\n", expect_number());
		}

		expect('-');
		printf("	sub rax, %d\n", expect_number());
	}

	printf("	ret\n");
	return 0;
}

新しいプログラミング言語を作る イコール コンパイラを作る ってことなのか。
低レイヤは学習コストが高いけど、一生物の知識がつくな。