■掲示板に戻る■ ■過去ログ倉庫めにゅーに戻る■
スクリプト自作
1 名前: 名無しさん@お腹いっぱい。 投稿日: 2000/08/08(火) 08:11
について


2 名前: 簡単なマトメです 投稿日: 2000/08/08(火) 08:14
字句解析  文字列から単語レベル(トークン)に分解します
      ひとつのトークンは 定数、演算子、予約語 識別子に分解されます
構文解析
 トークンの組み合わせから、そのプログラムの意味を調べます。
 if goto 等の処理は実際、比較的簡単です。難しいのは式の処理ですね
 一番簡単でポピューラーな方法は
 再帰下降法(再帰的下向き構文解析) recursive-descent parsing
  常にトークンを一つ先読みし、再帰的に式を解釈してゆきます
  例:
  <式>expression は
    項のみ
    (+/-/!)項
    項(+/-)項
    項(&/|/^)項
    項(</>/=/<>,!=/=>/=<)項 結果は0または1
  <項>   項と因子を分ける理由は、演算の優先順位を付ける為
    因子
    因子(*,/,%)因子
  <因子>
    数
    (式)
    ラベル
    関数名(式,...,式)
 として、式から解析をスタートさせます。
 そのまま処理させるとインタープリタ。結果を一旦出力するのがコンパイラです
  # 比較演算子の優先度をさらに低くするなら もう一段クラス別けをします
  簡単なコンパイラは後置記法(postfix notation)を使います
  例:
  <式>項のみ-----無視
    (+/-/!)項------スタックを対象に演算し結果をスタックに残す
    項(演算子)項---スタックから1つ取りスタックと演算し結果をスタックに残す
  <項>  式と同じ
  <因子> 結果をスタックに詰む処理を出力


3 名前: 本家のポインタ 投稿日: 2000/08/08(火) 08:48
ギコbasic
http://mentai.2ch.net/test/read.cgi?bbs=prog&key=961558082

Java版
http://mentai.2ch.net/test/read.cgi?bbs=prog&key=964090458&ls=30

RPG等ゲーム用
http://mentai.2ch.net/test/read.cgi?bbs=prog&key=963497207&ls=50


4 名前: 勝手に読む講義 投稿日: 2000/08/08(火) 16:26
kono先生 萌え
http://rananim.ie.u-ryukyu.ac.jp/kono/lecture/1999/compiler/compiler.html
http://rananim.ie.u-ryukyu.ac.jp/kono/lecture/1998/compiler/compiler.html
http://rananim.ie.u-ryukyu.ac.jp/kono/lecture/1997/compiler/compiler.html
http://rananim.ie.u-ryukyu.ac.jp/kono/lecture/1996/compiler/compiler.html


5 名前: 500h 投稿日: 2000/08/08(火) 16:34
ども、こっちに移ったんですね。
私が作成中の#define定義スクリプトモドキは一応運用手前レベルに来ました。
命令数が 00〜3f までの64種類に限られるのがアレですが、
1命令4バイトの固定長なんで、色々と4バイトの中に詰め込んでしまいました。
2さんとは趣きも思想も違いますが、コルーチンのさらに下の制御方法として
開発の過程でも安定性の高いサンプル提出が可能であると勝手に思い込んで
このまま組んでいくことにします。
応用される方もいないと思いますが、
まあ、こういうやり方もあるっちゅー事で(笑)



6 名前: 2>5 投稿日: 2000/08/08(火) 16:45
こんにちは、あれからWebで色々調べて 同じような組みかたなさってる方を見つけました

ここの12回です
http://www.sun-inet.or.jp/~yaneurao/rsp/rsp11to18.html

CPUはx86系では無いという事ですが、x86系だと沢山のスレッドを
同時に動かせるような場合は、仮想マシン的な処理の方が効率が良さ
そうに思えて来ました。


7 名前: 勝手に読む講義 投稿日: 2000/08/08(火) 17:46
群馬大学工学部情報工学科 中野眞一先生
http://www.msc.cs.gunma-u.ac.jp/~nakano/Compiler/


8 名前: 500h 投稿日: 2000/08/08(火) 22:15
私が組んでいるスクリプトモドキは教科書をみて作ったわけじゃなくて
もともとアセンブラでやってた仕事で作業分担させたいと思ったとき
アシスタントがアセンブラ使えないんで作ったモノです。
少なくとも命令サイドのバグがなければ機嫌よく動くので
思わぬメリットがありました。
ただ動作させていたのが8ビットCPUだったので
オーバヘッドもかなりのものでしたが(笑)

現状のターゲットではオーバヘッドも気にする必要がないんで
かなり凝ったことができそうです、
定数と変数を同じ命令で使えたり

ADD val1,1     // val1 = val1 + 1
ADD val1,$$(val2) // val1 = val1 + val2

とかですね。
8ビットの頃はこんな芸当させると覿面に重くなったものですが・・・(笑)

手元にあるスクリプトモドキモジュールはIFも実装しあやしげですが
動作しています。やれやれ。


9 名前: 名無しさん@お腹いっぱい。 投稿日: 2000/08/09(水) 02:28
>8
なんかどっかで聞いたことある話だぞ。
そのアシスタント、ふてくされてなかった?
それとも、ありがちな話?


10 名前: 500h 投稿日: 2000/08/09(水) 02:33
>9さん
うーん、10年近く昔の話ですけどね。
ありがちな話なんじゃないかな?
ふてくされるどころか、2,3日のトレーニングで
バリバリ仕事できたから、自信ついたみたいで、
暇をみてはアセンブラ教えてあげました。
固有の仕様にまつわる部分はダイレクトにアセンブラの
モジュールを書けるようにしてそこを担当させたりね。


11 名前: たぶん 投稿日: 2000/08/09(水) 10:36
自作スクリプトの有用な場面てのは
1)パートナーとの技術格差が大きい場合にも効率良く作業分担出来る
2)出先で開発ツールを持ち出さずに修正出来る
3)ユーザーにカスタマイズの自由度を与える
4)チューニングが容易になり、仕様変更に強くなる

てな感じかな。
ROMゲームなんかでは2や3のメリットは少ないだろうけど

私も仕事で何度か貧弱なスクリプト作った事あるけど、最初、
再帰下降法も知らないで出来る範囲でやったからアセンブラ
みたいなスクリプトになってしまった経験がある。次はマトモ
なのと思いつつ、最初のスタイルから抜けられない。
でも、次は頑張ろう。


12 名前: 500h 投稿日: 2000/08/09(水) 11:34
>再帰下降法も知らないで出来る範囲でやったからアセンブラ
>みたいなスクリプトになってしまった経験がある。
私がいま組んでるのはまさしくソレです。
でも結果的にcaseなりテーブルジャンプで処理できる分、速いかなーと。
1)と4)のメリットは大きいですよ、ひたすら調整になるゲームの場合は
メインプログラマがデバックに集中できますから。



13 名前: たぶん 投稿日: 2000/08/09(水) 12:10
自作スクリプトは「スクリプトである」事で目的を達成出来る
から、凝る必要は無いって事かな。
500hさんレスありがとう。


14 名前: 500h 投稿日: 2000/08/09(水) 12:20
>13さん
いえいえ。
っていうか、トレードオフの問題ですね。
時間が無限にあればちゃんとした言語作ってみたいです。
今時のゲーム機ならインタープリタでも処理速度は十分足りますしね。
2DならシューティングでもOKでしょう。
#define型(アセンブラもどき)のメリットはコード変換が
コンパイラ(アセンブラ)で済むっていう部分でしょうか。
機械語みたいなスクリプトも棄てたものじゃないと思いますよ。
なにせハードで実行できる文法形式だけに
それを処理するプログラムを書いても重くなる要因はありません。
スタックの保護どころか、ポインタまで仮想マシンレベルですから
暴走する類のバグは開発過程でもあまり発生しません。
これはある程度まで進めて調整しなくてはならないゲームでは大きなメリットです。
技術的に貧弱でも結果的にバランスがとれれば
それはそれで良いものではないでしょうか。



15 名前: たぶん 投稿日: 2000/08/10(木) 09:24
それは正しい事だけど、でも若者には夢も必要かも。
ここで年寄りが現実話を続けては向上心を奪う結果になってはと少し心配。


16 名前: 500h 投稿日: 2000/08/10(木) 16:42
>たぶんさん
うーん。そうですねぇ。小賢しい知恵といえば小賢しいし。
では、しばらく夢でも語りましょうか?(笑)



17 名前: たぶん 投稿日: 2000/08/12(土) 13:59
/*******************************************************
再帰下降法によるコンパイラ用構文解析器の原形
  <式>expression
    項 のみ
    項(+,-) 項 [(+,-)項 ]
  <項>term 項と因子を分ける理由は、演算の優先順位を付ける為
    因子
    因子(*,/)因子[(*,/)因子]
  <因子>factor
    数 [0〜9]
    (式)
*******************************************************/
struct TokenBase{
 enum Types{NIL,CHAR,STRING,NUM,LABEL,FUNC}; //トークンの種類
virtual int  Char()=0; //1文字を切り出す
virtual int  number()=0;
virtual Types type()=0;
virtual void Next()=0;
};

struct Script{
 TokenBase *token;
void expression();
void term();
void factor();
virtual void err(char const * const )=0;
virtual void number()=0;
virtual void op(int opc)=0;
};
void Script::expression()
{ int ch;
 term();
while( token->type()==token->CHAR)
{switch( ch=token->Char() ) {
    case '+':
    case '-':
     token->Next(); term(); //次の項有
     op(ch);
     break;
    default: return ;
   }
}
}
void Script::term()
{ int ch;
 factor();
while( token->type()==token->CHAR)
{switch( ch=token->Char() ) {
    case '*':
    case '/':
     token->Next(); factor(); //次の因子有り
     op(ch);
     break;
    default: return ;
   }
}
}
void Script::factor()
{
switch(token->type())
 {
case token->CHAR:
if(token->Char()=='(')
  {token->Next();
  expression();
  if(!(token->type()==token->CHAR && token->Char()==')')) err("括弧が閉じない");
  token->Next();
  };break;
case token->NUM: number(); break;
  }
}


18 名前: その試験ルーチン 投稿日: 2000/08/12(土) 14:02
/********************************************************
 先読みするので改行等、最後に余計な文字が必要
*********************************************************/
#include <stdio.h>
#include <stdlib.h>
int main()
{
struct Token:TokenBase{
int IsSpace(int c){ return c==' ' || c=='\t';}
int IsNum(int c){ return ( ('0' <= c) && (c <= '9') ); }

 Script *parent;
 enum Types types; //トークンの種類
Types type(){ return types;};
 int other;
int  Char(){if(types==CHAR)return other;else parent->err("CHARでない");return 0;};
 int num;
int  number(){if(types==NUM)return num;else parent->err("数でない");return 0;};
char buf[100];
int ch;
void getNum(){
 int k=0;char *s=buf;
 while(IsNum(ch)){
  *s++ = ch ;
  ch=getchar();
  if(k++ >= sizeof buf) {parent->err("数字バッファを越えた");return ;};
} *s = 0 ;
  sscanf(buf,"%d",&(num) );
   types=NUM;
   return ;
}

void Next()
 {
  while( IsSpace(ch) ) ch=getchar(); //文字が空白なら空白飛ばし
  if( IsNum(ch) )  { getNum();  return ; }
  types=CHAR; other = ch;  ch= getchar(); //先読
 }
 Token(){ ch=getchar();Next();}; //トークンは先読みして開始する
} tok;

struct testscr:Script
{
void err(char const * const msg)
 { puts(msg);
  exit(-1);
 }
virtual void number()
 { int num=token->number();token->Next();
  printf(" push #%d\n",num);
 }
virtual void op(int opc)
 { printf(" pop eax / pop edx / eax%c=edx / push eax\n",opc);
 }
testscr(Token *t)
 {
 token=t;
 t->parent=this;
 }
} scr(&tok);
 scr.expression();
}



19 名前: 17 投稿日: 2000/08/12(土) 14:09
2さんが解説しておられる再帰下降法の例です
1)factorに変数、関数の処理を追加する
2)expressionより下位を定義する
3)再度クラス定義を検討し、struct->classにする
4)下位クラスで機械語を直接吐かせる
 という手順でコンパイラタイプのスクリプトに発展出来ると思います


20 名前: 18を実行すると 投稿日: 2000/08/12(土) 14:34
1+2*(3+4)+5 という入力から
push #1
push #2
push #3
push #4
pop eax / pop edx / eax+=edx / push eax
pop eax / pop edx / eax*=edx / push eax
pop eax / pop edx / eax+=edx / push eax
push #5
pop eax / pop edx / eax+=edx / push eax
のような出力になりますが、
pop edx / pop eax / eax X= edx / push eax
と読み替えて下さい


21 名前: 名無しさん@お腹いっぱい。 投稿日: 2000/08/12(土) 23:56
あれからC++の部分をCに書換え、さらに後置記法の部分を
たとえば、
1+2*x+3*y*(x+y)が
L0=x,L1=y と置換して R0〜はレジスタ変数として
R1= 2 * L0
R0= 1 + R1
R1= 3 * L1
R2=L0 + L1
R1=R1 * R2
R0=R0 + R1 というふうに展開するのが出来るようになりました。
ただ演算子毎に、定数とメモリの組み合わせが必要になりますが

誰か一緒に苦労したい人居れば 途中経過を公開しますが


22 名前: 名無しさん@お腹いっぱい。 投稿日: 2000/08/13(日) 01:19
こんな感じで実行出来るPCODEを吐くのが一番楽かもしれません

int mem[256];  //変数用メモリ
int stack[32];  //作業用メモリ
int *sp=&stack[31];
unsigned char *dp; //実行中のPCODEを示す
int wk;
int (*Proc)(int **sp);
// PCODEの実行
switch(*dp++)
 {
 case  JnZR16: if(*sp++)
 case JumpR16: dp+=*(short int *)(dp);dp+=2;break;//次の2バイトでJUMP
 case FuncD32: Proc=(int (*)(int **sp))(*sp++);wk=Proc(&sp);*(--sp)=wk;break;
 case ProcD32: Proc=(int (*)(int **sp))(*sp++);wk=Proc(&sp);break;
 case PushD32: --sp;(*sp)=*(int *)(dp);dp+=4;break;//次の4バイトを整数即値:
 case PushMem: --sp;(*sp)=mem[*dp++];break;
 case PushMsg: a=(*sp++);--sp=(int)dp;dp+=a;break;
 case   '=': mem[*dp++] = (*sp);sp++;break;//変数に代入
 case   '<': (*sp+1) = (*sp+1) < (*sp);sp++;break;
 case   '>': (*sp+1) = (*sp+1) > (*sp);sp++;break;
 case   opEQ: (*sp+1) = (*sp+1) == (*sp);sp++;break;
 case   opNE: (*sp+1) = (*sp+1) != (*sp);sp++;break;
 case   opGE: (*sp+1) = (*sp+1) >= (*sp);sp++;break;
 case   opLE: (*sp+1) = (*sp+1) <= (*sp);sp++;break;
 case   '&': (*sp+1) = (*sp+1) & (*sp);sp++;break;
 case   '|': (*sp+1) = (*sp+1) | (*sp);sp++;break;
 case   '^': (*sp+1) = (*sp+1) ^ (*sp);sp++;break;
 case   '*': (*sp+1) = (*sp+1) * (*sp);sp++;break;
 case   '/': (*sp+1) = (*sp+1) / (*sp);sp++;break;
 case   '+': (*sp+1) = (*sp+1) + (*sp);sp++;break;
 case   '-': (*sp+1) = (*sp+1) - (*sp);sp++;break;
 case  ProgEnd:
 }