スクリプト言語をつくります

テスト期間とかなにそれおいしいので赤点とらない程度の勉強で。
DxLib使ってCでRPGをみんなで作ろうってことになって、イベントデータを外部ファイルに置くことに。
ここでイベントに制御構造とかを持たせようとすると、イベントデータを読み込んでそれを解釈するインタプリタが必要になった。せっかくだし自分の腕試しがてらスクリプト言語をつくってみよっかなって思う。

とりあえず言語仕様とか
構文解析に先読みとかいう高度なことをしないように、前置記法を全面に。
・ifとかは評価ができる(=式。)だけどlambdaの概念いまいちわからんので関数は式じゃない。
・ゲームのための言語だし、bool型じゃなくてflag型ってかっこつけたい
・イベント(引数なし)と関数(引数あり)を用意する
・なんかこう、式たくさん!←

「こんなふうなのが実行できたらいいな」からBNFを書き起こせないかとたくらむ

var battle_f:flag true;
event battle_hoge
   if eq battle_f true then {
      var i:int,
      var num_win:int 0,
      #msg("えりっく", "チャンスは4回だ!2回勝てたらLv上げしてやろう"),
      loop let i 0; and lt i 10 lt num_win 2; inc i {
         #msg("えりっく", "#{i}回目!"),
         case #battle() of
            "win"  : {#msg("えりっく", "三<(^q^)>三"), inc num_win};
            "lose" :  #msg("えりっく", "m9(^q^)");
            "other":  #msg("えりっく", "\(^q^)/")
      },
      if gt num_win 1 then {
         #msg("えりっく", "ヤラレター"),
         inc $level
      } else 
         #msg("えりっく", "まけてやんのー^q^"),
      let battle_f false
   } else 
      #msg("えりっく", "もうたたかえないよ^q^").

わぁきもちわるい

とりあえずトークンの定義。
SYMBOLが記号()<>,.;とか。
OPERANDがincとかandとかltとかifとか。
VARTYPEがintとかflagとか。
RESVEDがvarとかeventとかtrueとか。
IDENTがmsgとかbattle_fとか。
STRINGが"えりっく"とか。
うん、こんなもんだろ。
BNFとか言ったけど正規表現ぽいのまぜてえりっくだけわかる定義。
::= '(' | ')' | '{' | '}' | ',' | '.' | ':' | ';' | '#'
::='loop' |'lt' | 'gt' | 'eq' |'and' | 'or' | 'let' | 'if' | 'else' |
      'add' | 'sub' | 'mul' | 'div' | 'mod' | 'inc' | 'dec'
::='int' | 'flag' | 'string'
::='event' | 'fun' | 'var' | 'true' | 'false'
::=[a-z][a-z0-9_]*
::='"'[^"]*'"'

コードにするとこんなかんじ

#define NUM_SYMBOL   9
#define NUM_OPERAND 16
#define NUM_VARTYPE  3
#define NUM_RESVED  21

enum e_token_type {
   TOKEN_TYPE_SYMBOL ,
   TOKEN_TYPE_OPERAND,
   TOKEN_TYPE_VARTYPE,
   TOKEN_TYPE_RESVED ,
   TOKEN_TYPE_NUMBER ,
   TOKEN_TYPE_IDENT  ,
   TOKEN_TYPE_STRING ,
   TOKEN_TYPE_EOF
};
enum e_operand {
   OP_LOOP,
   OP_LT, OP_GT, OP_EQ,
   OP_AND, OP_OR, OP_LET, OP_IF, OP_ELSE,
   OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_INC, OP_DEC
};
enum e_resved {
   RSV_EVENT, RSV_FUN, RSV_VAR, RSV_TRUE, RSV_FALSE
};

typedef struct _TOKEN {
   int type;
   union {
      char ch;
      int  num;
      char *ptr;
   } d;
   struct _TOKEN *next, *prev;
} TOKEN;

とりあえずさくっと字句解析作ってみた。

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "recrio.h"

static TOKEN *__chain;

const char symbol[NUM_SYMBOL] = {
   '(', ')', '{', '}', ',', '.', ':', ';', '#'
};
const char operand[NUM_OPERAND][16] = {
   "loop",
   "lt", "gt", "eq",
   "and", "or", "let", "if", "else",
   "add", "sub", "mul", "div", "mod", "inc", "dec"
};
const char vartype[NUM_VARTYPE][16] = {
   "int", "flag", "string"
};
const char resved[NUM_RESVED][16] = {
   "event", "fun", "var", "true", "false"
};

static TOKEN* new_token(void) {
   TOKEN *tok = (TOKEN*)malloc(sizeof(TOKEN));
   if(tok == NULL) {
      fprintf(stderr, "FATAL ERROR: Failed to allocate memory.\n");
      exit(EXIT_FAILURE);
   }
   tok->next = tok->prev = NULL;
   return tok;
}

void init_token_chain(TOKEN *chain) {
   chain = new_token();
   chain->prev = chain;
   __chain = chain;
}
void  end_token_chain(void) {
   while(__chain->prev != __chain) {
      TOKEN *temp;
      temp = __chain->prev;
      __chain->prev = __chain->prev->prev;
      if(temp->type == TOKEN_TYPE_STRING || temp->type == TOKEN_TYPE_IDENT) {
         free(temp->d.ptr);
      }
      free(temp);
   }
   free(__chain);
}

static void __add_token(TOKEN *tok) {
   __chain->prev->next = tok;
   tok->prev = __chain->prev;
   __chain->prev = tok;
   switch(tok->type) {
   case TOKEN_TYPE_SYMBOL : printf("[SYMBOL ]'%c'\n", tok->d.ch);break;
   case TOKEN_TYPE_OPERAND: printf("[OPERAND]%s\n", operand[tok->d.num]);break;
   case TOKEN_TYPE_VARTYPE: printf("[VARTYPE]%s\n", vartype[tok->d.num]);break;
   case TOKEN_TYPE_RESVED : printf("[RESVED ]%s\n", resved [tok->d.num]);break;
   case TOKEN_TYPE_NUMBER : printf("[NUMBER ]%d\n", tok->d.num);break;
   case TOKEN_TYPE_IDENT  : printf("[IDENT  ]%s\n", tok->d.ptr);break;
   case TOKEN_TYPE_STRING : printf("[STRING ]%s\n", tok->d.ptr);break;
   }
}

// TYPE = {symbol | operand | vartype | resved | number | ident | string}
#define add_token(TYPE, TOK) add_ ## TYPE ## _token(TOK)

static void add_symbol_token(char symbol) {
   TOKEN *tok = new_token();
   tok->type  = TOKEN_TYPE_SYMBOL;
   tok->d.ch  = symbol;
   __add_token(tok);
}
static void add_operand_token(int idx_operand) {
   TOKEN *tok = new_token();
   tok->type  = TOKEN_TYPE_OPERAND;
   tok->d.num = idx_operand;
   __add_token(tok);   
}
static void add_vartype_token(int idx_vartype) {
   TOKEN *tok  = new_token();
   tok->type   = TOKEN_TYPE_VARTYPE;
   tok->d.num  = idx_vartype;
   __add_token(tok);
}
static void add_resved_token(int idx_resved) {
   TOKEN *tok = new_token();
   tok->type  = TOKEN_TYPE_RESVED;
   tok->d.num = idx_resved;
   __add_token(tok);
}
static void add_number_token(int number) {
   TOKEN *tok = new_token();
   tok->type  = TOKEN_TYPE_NUMBER;
   tok->d.num = number;
   __add_token(tok);
}
static void add_ident_token(char *ident) {
   TOKEN *tok = new_token();
   tok->type  = TOKEN_TYPE_IDENT;
   tok->d.ptr = (char*)malloc(strlen(ident) + 1);
   strcpy(tok->d.ptr, ident);
   __add_token(tok);
}
static void add_string_token(char *string) {
   TOKEN *tok = new_token();
   tok->type  = TOKEN_TYPE_STRING;
   tok->d.ptr = (char*)malloc(strlen(string) + 1);
   strcpy(tok->d.ptr, string);
   __add_token(tok);
}


static int is_symbol(const int ch) {
   int i;
   for(i = 0; i < NUM_SYMBOL; ++i) if(symbol[i] == ch) return 1;
   return 0;
}
static int is_operand(const char *str) {
   int i;
   for(i = 0; i < NUM_OPERAND; ++i) if(!strcmp(str, operand[i])) return i;
   return -1;
}
static int is_vartype(const char *str) {
   int i;
   for(i = 0; i < NUM_VARTYPE; ++i) if(!strcmp(str, vartype[i])) return i;
   return -1;
}
static int is_resved(const char *str) {
   int i;
   for(i = 0; i < NUM_RESVED; ++i) if(!strcmp(str, resved[i])) return i;
   return -1;
}

TOKEN* tokenize(void) {
   int ch;
   while(1) {
      while(1) { // skip while space
         if((ch = getchar()) == EOF) goto TOKENIZE_END;
         if(!isspace(ch)) break;
      }
      // type:symbol
      if(is_symbol(ch)) {
         add_token(symbol, ch);
         continue;
      }
      // type:string
      if(ch == '"') {
         char str[1024];
         char *p;
         for(p = str; p != &str[1024-2]; ++p) {
            if((ch = getchar()) == EOF) goto TOKENIZE_END;
            if(ch == '"') break;
            *p = ch;
         }
         *p = '\0';
         add_token(string, str);
         continue;
      }
      // type:number
      if(isdigit(ch)) {
         char str[128];
         char *p;
         for(p = str, *(p++) = ch; p != &str[128-2]; ++p) {
            if((ch = getchar()) == EOF) goto TOKENIZE_END;
            if(!isdigit(ch)) break;
            *p = ch;
         }
         *p = '\0';
         add_token(number, atoi(str));
      }
      // type:(ident|operand|resved)
      if(isalpha(ch) || ch == '_') {
         int idx;
         char str[1024];
         char *p;
         for(p = str, *(p++) = ch; p != &str[1024-2]; ++p) {
            if((ch = getchar()) == EOF) goto TOKENIZE_END;
            if(!isalnum(ch) && ch != '_') break;
            *p = ch;
         }
         *p = '\0';
         if     ((idx = is_operand(str)) != -1) add_token(operand, idx);
         else if((idx = is_vartype(str)) != -1) add_token(vartype, idx);
         else if((idx = is_resved (str)) != -1) add_token(resved , idx);
         else   add_token(ident, str);
      }
   }
TOKENIZE_END:
   /* add end of file identifier */ {
      TOKEN *temp = new_token();
      temp->type = TOKEN_TYPE_EOF;
      __add_token(temp);
   }
   return __chain->next;
}

実は構文解析の途中までできてるのもあるんだけど、ちょっと不満な点があるからこれから書きなおす。