あなたは素晴らしいプロジェクトを選びました - インタプリタを書くのはとても楽しいものです!
しかし、最初に、通訳者とは何を意味するのでしょうか? 通訳者には様々なタイプがいます。
純粋なインタープリターがあり、各言語要素を見つけたときに単純に解釈します。これらは最も書きやすく、最も遅いです。
ステップアップとして、各言語要素をある種の内部形式に変換し、それを解釈します。相変わらず書きやすい。
次のステップは、実際に言語を解析し、構文ツリーを生成してから解釈することです。これを書くのは少し難しいですが、何度かやるとかなり簡単になります。
構文ツリーがあれば、カスタム スタック仮想マシン用のコードをかなり簡単に生成できます。JVM や CLR などの既存の仮想マシン用のコードを生成するのは、はるかに困難なプロジェクトです。
プログラミングでは、ほとんどのエンジニアリングの取り組みと同様に、特に複雑なプロジェクトでは、慎重な計画が大いに役立ちます。
したがって、最初のステップは、作成するインタープリターのタイプを決定することです。多数のコンパイラに関する本を読んだことがない場合 (たとえば、Niklaus Wirth の「Compiler Construction」は、このテーマの最良の入門書の 1 つとして常にお勧めします。現在、Web 上で PDF 形式で無料で入手できます)。純粋な通訳者と一緒に行くこと。
ただし、追加の計画を立てる必要があります。何を解釈しようとしているのかを厳密に定義する必要があります。EBNF はこれに最適です。初心者向けの EBNF の紹介については、http://www.semware.com/html/compiler.htmlで Simple Compiler の最初の 3 つの部分を読んでください。これは高校レベルで書かれており、簡単に理解できるはずです。はい、最初に子供たちに試してみました:-)
何を解釈したいかを定義したら、インタプリタを書く準備が整います。
抽象的に言えば、単純なインタープリターは、スキャナー (技術的には字句解析器)、パーサー、および評価器に分割されます。単純な純粋なインターポレーターの場合、パーサーとエバリュエーターが組み合わされます。
スキャナーは書きやすく、テストも簡単なので、ここでは時間を割きません。単純なスキャナーの作成に関する情報については、前述のリンクを参照してください。
(たとえば)gotoステートメントを定義しましょう:
gotostmt -> 'goto' integer
integer -> [0-9]+
これは、トークン「goto」(スキャナーによって配信されたもの)を見ると、それに続くことができる唯一のものは整数であることを示しています。そして、整数は単なる数字の文字列です。
疑似コードでは、これを次のように処理します。
(token - 現在のトークンであり、スキャナーを介して返された現在の要素です)
loop
if token == "goto"
goto_stmt()
elseif token == "gosub"
gosub_stmt()
elseif token == .....
endloop
proc goto_stmt()
expect("goto") -- redundant, but used to skip over goto
if is_numeric(token)
--now, somehow set the instruction pointer at the requested line
else
error("expecting a line number, found '%s'\n", token)
end
end
proc expect(s)
if s == token
getsym()
return true
end
error("Expecting '%s', found: '%s'\n", curr_token, s)
end
それがどれほど簡単か分かりますか?実際、単純なインタープリターで理解するのが難しいのは、式の処理だけです。これらを処理するための適切なレシピは次のとおりです。 http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm前述の参考文献と組み合わせると、BASIC で遭遇するような式を処理するのに十分なはずです。 .
わかりました、具体例の時間です。これは、Tiny BASIC の拡張バージョンを処理する、より大きな「純粋なインタープリター」からのものです (しかし、Tiny Star Trek を実行するのに十分な大きさです :-) )
/*------------------------------------------------------------------------
Simple example, pure interpreter, only supports 'goto'
------------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <setjmp.h>
#include <ctype.h>
enum {False=0, True=1, Max_Lines=300, Max_Len=130};
char *text[Max_Lines+1]; /* array of program lines */
int textp; /* used by scanner - ptr in current line */
char tok[Max_Len+1]; /* the current token */
int cur_line; /* the current line number */
int ch; /* current character */
int num; /* populated if token is an integer */
jmp_buf restart;
int error(const char *fmt, ...) {
va_list ap;
char buf[200];
va_start(ap, fmt);
vsprintf(buf, fmt, ap);
va_end(ap);
printf("%s\n", buf);
longjmp(restart, 1);
return 0;
}
int is_eol(void) {
return ch == '\0' || ch == '\n';
}
void get_ch(void) {
ch = text[cur_line][textp];
if (!is_eol())
textp++;
}
void getsym(void) {
char *cp = tok;
while (ch <= ' ') {
if (is_eol()) {
*cp = '\0';
return;
}
get_ch();
}
if (isalpha(ch)) {
for (; !is_eol() && isalpha(ch); get_ch()) {
*cp++ = (char)ch;
}
*cp = '\0';
} else if (isdigit(ch)) {
for (; !is_eol() && isdigit(ch); get_ch()) {
*cp++ = (char)ch;
}
*cp = '\0';
num = atoi(tok);
} else
error("What? '%c'", ch);
}
void init_getsym(const int n) {
cur_line = n;
textp = 0;
ch = ' ';
getsym();
}
void skip_to_eol(void) {
tok[0] = '\0';
while (!is_eol())
get_ch();
}
int accept(const char s[]) {
if (strcmp(tok, s) == 0) {
getsym();
return True;
}
return False;
}
int expect(const char s[]) {
return accept(s) ? True : error("Expecting '%s', found: %s", s, tok);
}
int valid_line_num(void) {
if (num > 0 && num <= Max_Lines)
return True;
return error("Line number must be between 1 and %d", Max_Lines);
}
void goto_line(void) {
if (valid_line_num())
init_getsym(num);
}
void goto_stmt(void) {
if (isdigit(tok[0]))
goto_line();
else
error("Expecting line number, found: '%s'", tok);
}
void do_cmd(void) {
for (;;) {
while (tok[0] == '\0') {
if (cur_line == 0 || cur_line >= Max_Lines)
return;
init_getsym(cur_line + 1);
}
if (accept("bye")) {
printf("That's all folks!\n");
exit(0);
} else if (accept("run")) {
init_getsym(1);
} else if (accept("goto")) {
goto_stmt();
} else {
error("Unknown token '%s' at line %d", tok, cur_line); return;
}
}
}
int main() {
int i;
for (i = 0; i <= Max_Lines; i++) {
text[i] = calloc(sizeof(char), (Max_Len + 1));
}
setjmp(restart);
for (;;) {
printf("> ");
while (fgets(text[0], Max_Len, stdin) == NULL)
;
if (text[0][0] != '\0') {
init_getsym(0);
if (isdigit(tok[0])) {
if (valid_line_num())
strcpy(text[num], &text[0][textp]);
} else
do_cmd();
}
}
}
うまくいけば、それはあなたが始めるのに十分であることを願っています. 楽しむ!