自作言語を作り始めた話
久々のブログ更新でこの1年何もやってなかったことが露呈してしまう。
今回は何を血迷ったか急にプログラミング言語が作りたくなったので、作ってみることにしました。
プログラミング言語 Sobaの開発
とりあえず、四則演算とかできるところまでは作ってみようというわけで、やってみました。
実際に実装した機能は、
- 四則演算
- 変数代入, 参照
- if文
大したものはやってないです。
概要
今回つかったツールは以下のようになります。
Bisonは構文解析を行うパーサーを生成するツールで、yaccの上位互換となっています。
Flexは字句解析器とよばれ、文字列の変換抽出を行います。
実際に、Flexでプログラム中のキーワード(if, for, 演算子、数字など)を抽出し、それをBisonに渡して、Bisonはキーワードの組み合わせを解析し、その組み合わせごとに決められた処理を行わせます。
実装
実際に私が書いたコードは以下の2つのみです。
- soba.l: 字句解析を行ってキーワードをbisonに渡します。
- soba.y: パーサーを生成し、パターンごとの処理をさせます。
src/soba.l
%{ #include <stdio.h> #include <string.h> #include "soba.tab.h" int yywrap(void) { return 1; } %} %% "udon" { fprintf(stderr, "Syntax Error: %s\n", yytext); exit(1); } [+\-*/%=<>:;] { return yytext[0]; } "\n" return LF; "if" return IF; "for" return FOR; "in" return IN; "print" return PRINT; "println" return PRINTLN; [ \t]+ {} \".*\" { yytext[strlen(yytext)-1] = '\0'; yylval.string = strdup(&yytext[1]); printf("%s\n", yylval.string); return STR; } [1-9][0-9]\.\.[1-9][0-9] { yylval.string = strdup(yytext); return RANGE; } [1-9]?[0-9]* { int temp; // 入力から実数を取得 sscanf(yytext, "%d", &temp); yylval.int_value = temp; return INTEGER; } [0-9]*\.[0-9]* { double temp; sscanf(yytext, "%lf", &temp); yylval.double_value = temp; return FLOAT; } [a-zA-Z_]+ { yylval.string = strdup(yytext); return(VAR); } . { fprintf(stderr, "Syntax Error: %s\n", yytext); exit(1); } %%
src/soba.y
%{ #include <stdio.h> #include <stdlib.h> #include <string.h> #define YYDEBUG 1 #define VARSIZE 255 typedef struct { char *name; double value; } variable; int var_used = 0; variable var[VARSIZE]; double get_value(char *name); int substitution(char *name, double value); %} %union { int int_value; double double_value; char *string; } %token <int_value> INTEGER %token <double_value> FLOAT %token <string> VAR STR RANGE %token LF IF PRINT PRINTLN FOR IN %type <int_value> block expr number if_stmt %type <string> string %start program %left '+' '-' %left '*' '/' '%' %% program : | PRINT block LF { printf("%d", $2); } | PRINTLN block LF { printf("%d\n", $2); } | PRINT string LF { printf("%s", $2); } | PRINTLN string LF { printf("%s\n", $2); } | program PRINT block LF { printf("%f", $3); } | program PRINTLN block LF { printf("%d\n", $3); } | program PRINT string LF { printf("%s", $3); } | program PRINTLN string LF { printf("%s\n", $3); } | program block LF { printf("--> %d\n", $2);} | string LF { printf("--> %s\n", $1); } | block LF { printf("--> %d\n", $1);} ; string : STR { $$ = $1; } ; block : expr { $$ = $1; } | VAR '=' expr { substitution($1, $3); $$ = $3; } | if_stmt { $$ = $1; } ; if_stmt : IF expr ':' block { if ( $2 != 0 ) $$ = $4; else $$ = 0; } | block IF block { if ( $3 != 0 ) $$ = $3; else $$ = 0; } ; expr : number { $$ = $1; } | expr '+' expr { $$ = $1 + $3; } | expr '-' expr { $$ = $1 - $3; } | expr '*' expr { $$ = $1 * $3; } | expr '/' expr { $$ = $1 / $3; } | expr '%' expr { $$ = $1 % $3; } ; number : INTEGER { $$ = (double)$1; } | FLOAT { $$ = $1; } | VAR { $$ = get_value($1); } ; %% int search_variable(char *name) { for ( int i = 0; i < var_used; i++ ) { if ( !strcmp(var[i].name, name) ) { return i; } } return -1; } int substitution(char *name, double value) { int i = search_variable(name); if ( i == -1 ) { var[var_used].name = strdup(name); var[var_used].value = value; var_used++; } else { var[i].value = value; } return 0; } double get_value(char *name) { int i = search_variable(name); if ( i != -1 ) { return var[i].value; } printf("%s is not definded\n", name); return 0; } int yyerror(char const *str) { extern char *yytext; fprintf(stderr, "Syntax Error: %s\n", yytext); return 0; } int main(int argc, char* argv[]) { extern int yyparse(void); extern FILE *yyin; if ( argc < 2 ) { yyin = stdin; } else { yyin = fopen(argv[1], "r"); } do { if (yyparse()) { fprintf(stderr, "Error Occured!\n"); exit(1); } } while(!feof(yyin)); }
実行結果
…電卓っすね。
今後の実装予定(言い訳フェイズ)
実装力の限界ッ!
文字列とfor文を今せっせと書いてるところです。
早いとこFizzBuzzできるようになりたいなあ。
今回内容が薄いのは今後に期待という感じでよろしくお願いします。
一応ここに、置いときますね。
https://github.com/guni973/soba
B3になったときのコンパイラの授業が楽しみでならない。
今後も機能追加、改善していくつもりなので、よろしくです。