LoginSignup
1
2

GAME言語インタプリタ on Linux 【GAMELinux】

Last updated at Posted at 2023-01-28

昔懐かしのGAME言語のインタプリタをCで書いてみました。

資料が少ないもので、オリジナルとは少し変わっていると思いますが、一応Linux上での実装ということで、ご勘弁いただきたいと思います。

GAME言語は今は廃れてしまいましたが、1980年前後にマイコンで手軽に使える処理系として一世を風靡しました。僕もGAME言語を参考に独自のインタプリタ・コンパイラを作って遊んでいたものです。

このプログラムはまだ未完成な所があり、エラーチェックなどが十分ではないです。実用とは程遠いものですが、まあ、遊びだと思って下さい。一応制御構文などは全部動きます。

コンパイル、インストール、実行

カレントディレクトリにgamelinux.cを置き、コンソールで、cc gamelinux.c -o gamelinuxとしてコンパイルて下さい。

インストールは、sudo cp gamelinux /usr/bin/として、/usr/bin/にgamelinuxを配置するだけです。

実行は、gamelinux [file.gm]として動かして下さい。
gamelinux では、シバンをつけることができます。シバンを付けて実行権を付けると、Linuxのシェルからスクリプトをコマンドのように実行できます。

シバンのサンプル

sample.gm
#!/usr/bin/gamelinux

使い方

このプログラムは引数なしで実行させると、プロンプトがでて、キーボードから入力を取り、コマンドを実行できます。

GAME言語のファイル拡張子はgmであるらしいので、ここではそれに則ります。

サンプルプログラムは、インタプリタ起動時に引数で指定すると読み込まれます。
コマンドプロンプトからは*ld filename.gmとして読み込んで下さい。#=1でrunです。

*がついているコマンドはオプショナルコマンドで、ユーザーが追加できるようにしてあります。(と言ってもインタプリタのソースを書き換える必要がありますが)

エディタはついてないです。行末が0xa(LF)で終わるテキストを読み込めます。

文法

・Jun Mizutani氏のGAME言語の文法を修正してアップします

<行番号>   1 〜 32767
<10進定数> 0 〜 65535
<16進定数> $0000 〜 $FFFF
<文字定数> "文字"
<変数名>   A 〜 Z または冗長形(ABC等 先頭1文字が有効)

<2バイト配列> ::= 変数名 ( <式> )
                  変数の値 + 2 * 式の値 のアドレスの内容を値とする.

<1バイト配列> ::= 変数名 : <式> )
                  変数の値 + 式の値 のアドレスの内容を値とする.

<定数> ::= <10進定数> | <16進定数> | <文字定数>

<変数> ::= <変数名> | <1バイト配列> | <2バイト配列>

<式> ::= <項> | <項> <二項演算子> <項>

<項> ::= <定数> | <変数> | <配列> |( <式> )| <単項演算子> <項> |  $  | ?
              $ は一文字入力、? は数値入力

<二項演算子> ::= + | - | * | / | = | <> | < | > | <= | >=
                比較演算 は 真:1, 偽:0の値を取る.

<単項演算子> ::= - | + | % | ' | #
                 + は絶対値, % は直前に実行した除算の余り,
                 ' は乱数, #は否定.
<行> ::= <行番号> スペース <文> [ 空白 <文> ] 改行
         | <行番号> スペース以外の文字 コメント 改行

<文>
    *AB              オプショナルコマンド この場合はABというコマンド
    <変数>=<式>     変数への代入
    #=<式>            <項>の値の行番号の文にジャンプ(GOTO)
                      行番号がなければ行番号より大きい最初の行へジャンプ
    #=-1              プログラムの終了(END)
    !=<式>            <項>の値の行番号のサブルーチンへジャンプ(GOSUB)
    ]                 サブルーチンから戻る(RETURN)
    ;=<式>            式の値が真の場合は次の文に進み,
                      偽の場合は次の行を実行.
    @                 DO
    @=(式)            UNTIL
    変数=初期値,終値 FOR
    @=式              NEXT
    /                 改行出力
    "文字列"          文字列出力
    ?=<式>            <項>の結果を数値出力 左詰め
    ??=<式>           <項>の結果を数値出力 16進4桁
    ?$=<式>           <項>の結果の下位1バイトを数値出力 16進2桁
    ?(n)=<式>         <項>の値の数値出力 n桁で右詰め
    $=<式>            <項>の値の下位バイトを文字コードとする1文字を出力
    .=<式>            <項>の値の下位バイトの数だけ空白を出力
    '=<式>            <項>の値で乱数シードを設定
このプログラムのオプショナルコマンドは次のとおりです。

*TN Trace on
*TF Trace off
*LD load game program
*QU quit
*SH invoke shell
*FM <n> for mode *fm 0の時、for文はGAME言語オリジナルのように振る舞い、*fm 1の時、普通の言語のように振る舞います。デフォルトは0です。

可搬性

一文字キー入力のgetch()関数だけがLinux固有のものです。getch()問題さえクリアできれば、ターミナルだったらどんな環境でも動くと思います。

履歴

・2023年1月31日 16ビット配列の取得の所にバグがあったので訂正。
・2023年2月1日 マイナーチェンジ&名前をgamelinuxにする。
・2023年2月2日 バグ修正 ver 0.9
・2023年2月3日 getch()関数を内包 ver 0.9.1
・2023年2月4日 for文修正 ver 0.9.2
・2023年2月8日 for文の普通の言語モードで動作するのを *FM 1に、GAME言語モードで動作するのを*FM 0にする。 ver 0.9.3

Sites

・GAME言語についてはこのサイトが詳しいようです。http://www43.tok2.com/home/cmpslv/Manual/GAME%20(J)(1979)(Ascii).pdf"

・ヘッダファイル、バイナリのdebパッケージ、ソース、サンプルプログラム等は、Githubにあります。

gamelinux.c
#include  <stdio.h>
#include  <ctype.h>
#include  <stdlib.h>
#include  <string.h>
#include <termios.h>
#include <unistd.h>

ushort variable['Z'-'A'+1]= {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
char *s;
u_char memory[65536];
char strbuff[256];
char pbuff[65536];
int psize=0;
int ln=0;
void *stack[65536];
int  sp=0;
int tron=0;
ushort mod=0;
int for_mode=0;

int getch()
{
    struct termios oldt, newt;
    int c;
    tcgetattr( STDIN_FILENO, &oldt );
    newt = oldt;
    newt.c_lflag &= ~ICANON;
    newt.c_lflag &= ~ECHO;
    tcsetattr( STDIN_FILENO, TCSANOW, &newt );
    c = getchar();
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
    return c;
}

int skipspc()
{
    while(*s==' ')
        s++;
}

int load_source(char *s)
{
    FILE *ifp=fopen(s,"r");
    int idx=0;
    if (ifp) {
        while(idx<sizeof(pbuff)-1) {
            int c;
            c=fgetc(ifp);
            if (c==EOF) break;
            pbuff[idx]=c;
            idx++;
        }
        fclose(ifp);
    }
    psize=idx;
    pbuff[idx]='\0';
}

int load_src()
{
    char fn[256],*op=fn;
    while(*s==' ') s++;
    while(*s!='\n') *op++=*s++;
    *op='\0';
    load_source(fn);
}


int syntaxerror()
{
    printf("\nSyntaxerror in %d",ln);
    fflush(stdout);
}

int skipc(char c)
{
    if (*s==c) {
        s++;
        return(0);
    }
    syntaxerror();
}

char operator2()
{
    char c;
    switch (c=*s) {
    case '=':
    case '+':
    case '-':
    case'*':
    case '/':
        s++;
        return(c);
    case '<':
        s++;
        if (*s=='>') {
            s++;
            return('N');
        } else if (*s=='=') {
            s++;
            return('A');
        } else return('<');
    case '>':
        s++;
        if (*s=='=') {
            s++;
            return('B');
        } else return('>');
    default:
        return(0);
    }
}

char operator1()
{
    char c;
    switch (c=*s) {
    case '+':
    case '-':
    case '\'':
    case '#':
    case '%':
        s++;
        return(c);
    default:
        return(0);
    }
}


char var()
{
    char c=0;
    if (isalpha(*s)) {
        c=*s;
        while(isalpha(*s))
            ++s;
    }
    return(c);
}

ushort getval16()
{
    int i;
    if (!isxdigit(*s)) return(-1);
    sscanf(s,"%x",&i);
    while(isxdigit(*s)) ++s;
    return((ushort)i);
}

ushort getval10()
{
    ushort v;
    if (!isdigit(*s)) return(-1);
    v=atoi(s);
    while(isdigit(*s))
        ++s;
    return(v);
}

char *str()
{
    char *si=strbuff;
    if (*s=='"') {
        skipc('"');
        while(*s!='"')
            *si++=*s++;
        skipc('"');
    }
    *si='\0';
    return(strbuff);
}

ushort const_()
{
    ushort v;
    if (*s=='"') {
        char *si;
        si=str();
        v=si[0]+si[1]*256;
        return(v);
    } else if (*s=='$') {
        skipc('$');
        v=getval16();
        return(v);
    } else if (isdigit(*s)) {
        v=getval10();
        return(v);
    }
    return(0);
}

ushort expression();

ushort term()
{
    ushort v;
    char c;
    skipspc();
    if (*s=='(') {            // (exp)
        skipc('(');
        v=expression();
        skipspc();
        skipc(')');
        return(v);
    } else if (c=var()) {
        if (*s==':') {       // V:exp)
            skipc(':');
            v=expression();
            skipc(')');
            v=memory[variable[toupper(c)-'A']+v];
            return(v);
        } else if (*s=='(') { // V(exp)
            skipc('(');
            v=expression();
            skipc(')');
            v=(((u_char)memory[variable[toupper(c)-'A']+v*2+1])<<8)+((u_char)memory[variable[toupper(c)-'A']+v*2]);
            return(v);
        } else {             // V
            v=variable[toupper(c)-'A'];
            return(v);
        }
    } else if ((*s=='$') && (!isxdigit(*(s+1)))) { // getch
        s++;
        return(getch());
    } else if (*s=='?') { // input
        int i;
        char buf[256];
        s++;
        scanf("%s",buf);
        if (buf[0]=='$')
            sscanf(buf+1,"%x",&i);
        else
            sscanf(buf,"%d",&i);
        return((ushort)i);
    } else if (v=const_()) // const
        return(v);
    else if (c=operator1(*s)) {  // term
        v=term();
        if (c=='-')
            return(-v);
        else if (c=='+')
            return(v<0?-v:v);
        else if (c=='#')
            return(!(v));
        else if (c=='\'') {
            int i;
            i=(ushort)(rand()%v);
            return(i);
        } else if (c=='%')
            return(mod);
    }
}

ushort expression()
{
    ushort v,v2;
    char c;
    skipspc();
    v=term();
    while(1) {
        if ((c=operator2())==0) break;
        v2=term();
        switch (c) {
        case '+':
            v+=v2;
            break;
        case '-':
            v-=v2;
            break;
        case '*':
            v*=v2;
            break;
        case '/':
            if (v2==0) {
                printf("Division by zero\n");
                v=-1;
                break;
            } else {
                mod=v%v2;
                v/=v2;
                break;
            }
        case '=':
            v=(v==v2);
            break;
        case '<':
            v=(v<v2);
            break;
        case 'N':
            v=(v!=v2);
            break;
        case 'A':
            v=(v<=v2);
            break;
        case '>':
            v=(v>v2);
            break;
        case 'B':
            v=(v>=v2);
            break;
        }
    }
    return(v);
}

int newline()
{
    while(1) {
        if (*s=='\n' || *s=='\0') break;
        s++;
    }
    if (*s=='\n') s++;
}

int searchline(ushort v)
{
    s=pbuff;
    if (s[0]=='#')
        while(*s++!='\n' );
    while(1) {
        if (s==pbuff+psize)
            return(-1);
        char *p=s;
        ushort n=getval10();
        s=p;
        if (n==-1)
            return(-1);
        if (n>=v)
            return(n);
        else
            newline();
    }
}

int go_to(ushort v)
{
    if (v==-1)
        ln=-1;
    else
        ln=searchline(v);
}

int go_sub(ushort v)
{
    stack[sp++]=s;
    go_to(v);
}

int return_()
{
    s=stack[--sp];
}

int do_()
{
    stack[sp++]=s;
}

int until_()
{
    char *p;
    ushort v;
    p=stack[--sp];
    v= expression();
    if (!v) {
        s=p;
        sp+=1;
    }
}

int next_()
{
    ushort *addr,to_,v;
    char *p;
    to_=(ushort )(ulong)stack[--sp];
    p=stack[--sp];
    addr=stack[--sp];
    *addr=v=expression();
    if (v<=to_) {
        s=p;
        sp+=3;
    }
}

int if_(ushort v)
{
    if (v==0) {
        newline();
        --s;
    }
}

int randseed(ushort v)
{
    srand(v);
}

int optional_command()
{
    char c1,c2;
    c1=toupper(*s++);
    c2=toupper(*s++);
    if (c1=='L' && c2=='D')
        load_src();
    else if (c1=='Q'&&c2=='U')
        exit(0);
    else if (c1=='T'&&c2=='N')
        tron=1;
    else if (c1=='T'&&c2=='F')
        tron=0;
    else if (c1=='S'&&c2=='H')
        system("bash");
    else if (c1=='F'&&c2=='M') {
        ushort v=expression();
        for_mode=v;
    } else syntaxerror();
}

int gameint()
{
    char c;
    ushort v;
    while(1) { /* line */
        c=*s;
        if (c=='\0')
            return(0);
        if (ln==-1)
            return(0);
        if (ln) {
            v=getval10();
            if (tron) {
                printf("[%d]",v);
                fflush(stdout);
            }
            ln=v;
            if (*s!=' ') {
                newline();
                continue;
            }
        }
        while(1) {
            c=*s;
            if (c=='\0') return(0);
            else if (c=='\n') {
                s++;
                break;
            } else if (c==' ') {
                skipspc();
                continue;
            } else if (c=='"') {
                printf("%s",str());
                fflush(stdout);
                continue;
            } else if (c=='/') {
                skipc('/');
                printf("\n");
                continue;
            } else if (c=='.') {
                skipc('.');
                skipc('=');
                v= expression();
                for(int i=0; i<v; i++)
                    printf(" ");
                fflush(stdout);
                continue;
            } else if (c=='*') {
                s++;
                optional_command();
                continue;
            } else if (c=='?') {
                s++;
                c=*s;
                if (c=='=') {
                    skipc('=');
                    v=expression();
                    printf("%d",v);
                    fflush(stdout);
                    continue;
                } else if (c=='?') {
                    skipc('?');
                    skipc('=');
                    v=expression();
                    printf("%04hx",v&0xffff);
                    fflush(stdout);
                    continue;
                } else if (c=='$') {
                    skipc('$');
                    skipc('=');
                    v=expression();
                    printf("%02x",v&0xff);
                    fflush(stdout);
                    continue;
                } else if (c=='(') {
                    char buf[12],b2[128];
                    int v2;
                    skipc('(');
                    v2=expression();
                    skipc(')');
                    skipc('=');
                    v=expression();
                    snprintf(buf,12,"%d",v2);
                    b2[0]='\0';
                    strcat(b2,"%");
                    strcat(b2,buf);
                    strcat(b2,"d");
                    printf(b2,v);
                    fflush(stdout);
                    continue;
                }
                syntaxerror();
                return(0);
            } else if (c=='\'') {
                s++;
                skipc('=');
                v=expression();
                randseed(v);
                continue;
            } else if (c=='$') {
                s++;
                skipc('=');
                v=expression();
                printf("%c",v);
                fflush(stdout);
                continue;
            } else if (c=='#') {
                s++;
                skipc('=');
                v=expression();
                go_to(v);
                break;
            } else if (c=='!') {
                s++;
                skipc('=');
                v=expression();
                go_sub(v);
                break;
            } else if (c==']') {
                s++;
                return_();
                continue;
            } else if (c=='@') {
                s++;
                if (*s=='=') {
                    skipc('=');
                    if (*s=='(') { /* until */
                        until_(v);
                        continue;
                    } else { /* next */
                        next_();
                        continue;
                    }
                } else { /* do */
                    do_();
                    continue;
                }
            } else if (c==';') {
                s++;
                skipc('=');
                v=expression();
                if_(v);
                continue;
            } else if (c=var()) {
                ushort v,*addr;
                if (*s==':') {
                    ushort v2,vidx;
                    s++;
                    vidx=expression();
                    skipc(')');
                    skipc('=');
                    v2=expression();
                    memory[variable[toupper(c)-'A']+vidx]=v2&0xff;
                    continue;
                } else if (*s=='(') {
                    ushort vidx;
                    s++;
                    vidx=expression();
                    skipc(')');
                    skipc('=');
                    v=expression();
                    addr=(ushort *)&(memory[variable[toupper(c)-'A']+vidx*2]);
                    *addr=v;
                } else {
                    skipc('=');
                    v=expression();
                    addr=(ushort *)(&(variable[toupper(c)-'A']));
                    *addr=v;
                }
                if (*s==',') {
                    ushort to_;
                    skipc(',');
                    to_=expression();
                    if (v>to_ && (for_mode)) { // if for mode not equals to 0
                        //  and initial_value > to_value, skip to the end of next NEXT
                        int eof=0;
                        while(*s!='@') {
                            if (*s=='\0') {
                                eof=1;
                                break;
                            }
                            s++;
                        }
                        if (!eof) {
                            skipc('@');
                            skipc('=');
                            expression();
                        }
                    } else {
                        stack[sp++]=(char *)addr;
                        stack[sp++]=s;
                        stack[sp++]=(char *)(ulong)to_;
                    }
                }
                continue;
            }
            syntaxerror();
            return(0);
        }
    }
}

int title()
{
    printf("gamelinux ver 0.9.3 by Taisuke Maekawa\n");
}

int commandline()
{
    char lb[256];
    char *f;
    ushort v;
    title();
    while(1) {
        printf("\n*Ready.\n");
        f=fgets(lb,sizeof(lb)-1,stdin);
        if (f==NULL)
            exit(1);
        ln=0;
        sp=0;
        s=lb;
        gameint();
    }
}

int main(int argc,char *argv[])
{
    psize=0;
    if (argc>=2) {
        load_source(argv[1]);
        go_to(1);
        gameint();
    } else commandline();
}

サンプルプログラム

test1

test.gm
#!/usr/bin/gamelinux
10"***comment ***"
100 "line 100 running "  !=10000
110 "line 110 running "  !=10000
120 "line 120 running "  !=10000
130 "line 130 running "  !=10000
9990 "end" /// #=-1
10000 "sub " 
10010 A=1,10 ?=A " " @=a+1 /
11000 ]

サンプルプログラム実行結果

line 100 running sub 1 2 3 4 5 6 7 8 9 10 
line 110 running sub 1 2 3 4 5 6 7 8 9 10 
line 120 running sub 1 2 3 4 5 6 7 8 9 10 
line 130 running sub 1 2 3 4 5 6 7 8 9 10 
end


お願い

バグを見つけた方がいたら、コメント欄にご一報下さい。m(_ _)m

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2