小ワザ
構文解析 †
構文解析とは、かかれている文章からコンピュータで意味を読み取ること、その解析のことです。
主にプログラム言語開発などで使用されています。
ここではアドベンチャーゲーム(以下、AVG,ADV)(サウンドノベル含む)でシナリオデータ*1を作成、読み込むための構文解析を取り上げています。
〜 目標 〜
- 命令 = 引数, 引数, 引数, ... の形式を読み取ります。
- 引数を文字列に対応させます
- // から始まる文字列はコメントとして無視します
- 日本語から始まる文字列は、そのまま表示します(「改行だけ」も含めて)
- テキストファイルから一行ずつ読み込んでみます(サンプル)
とりあえずサンプルです。HSP3公式掲示板より。
構文解析 v0.0 ( りさ )
+
| | 命令解析
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
-
|
|
!
-
|
-
|
!
-
|
|
!
|
#define SENTENCE_LEN 256 #define TOKEN_LEN 128 #define TOKEN_ELE 32
sdim sentence, SENTENCE_LEN
sdim token, TOKEN_LEN, TOKEN_ELE
n = 0
i = -1
ntoken = 0
sentence = "MusicPlay = 11 , 222, 3333 "
n = strlen( sentence )
repeat n
if ( peek( sentence, cnt ) != ' ' ) {
i = cnt
break
}
loop
if i = -1 { mes "空白しか存在しません" :stop }
getstr string1, sentence, i, ' '
getstr string2, sentence, i, '='
i += strsize
if ( strlen( string1 ) < strlen( string2 ) ) {
token(0) = string1
} else {
token(0) = string2
}
if token(0) = "" { mes "命令が存在しません" :stop }
repeat TOKEN_ELE
if n = i {
ntoken = cnt
break
}
getstr string, sentence, i, ','
i+ =strsize
tmp = int(string)
token( cnt+1 ) = str( tmp )
loop
mes "\""+sentence+"\""
mes
mes "["+token(0)+"]"
repeat ntoken
mes "("+token(cnt+1)+")"
loop
stop
|
|
構文解析 v0.1 ( osakana )
(引数の文字列中の半角スペースに対応してみました。)
+
| | 命令解析
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
-
|
|
!
-
|
|
!
| #module
#defcfunc DelSpace var p1,var p2
prmlen=strlen(p2)
index=0
last=0
repeat prmlen
if peek( p2, cnt)!' ' :index=cnt :break
loop
repeat prmlen-index
if peek( p2, prmlen-cnt-1)!' ' :last=prmlen-cnt :break
loop
prmsize=last-index
p1=strmid (p2,index,prmsize)
return prmsize
#global
#define SENTENCE_LEN 256 #define TOKEN_LEN 128 #define TOKEN_ELE 32
sdim sentence, SENTENCE_LEN
sdim token, TOKEN_LEN, TOKEN_ELE
dim len, TOKEN_ELE
n = 0
i = -1
ntoken = 0
sentence = "MusicPlay = 11 , 222, 3333 , , \"メッセージ\" , 1024 "
n = strlen( sentence )
repeat n
if ( peek( sentence, cnt ) != ' ' ) {
i = cnt
break
}
loop
if i = -1 { mes "空白しか存在しません" :stop }
getstr string, sentence, i, '='
i += strsize
len(0) = DelSpace( token(0), string)
if len(0) = 0 { mes "命令が存在しません" :stop }
repeat TOKEN_ELE
if n = i {
ntoken = cnt
break
}
getstr string, sentence, i, ','
i+ =strsize
len(cnt+1) = DelSpace( token( cnt+1 ), string)
if len(cnt+1) = 0 :token( cnt+1 )="-1"
loop
mes "\""+sentence+"\""
mes "トークンの数: "+ntoken
mes
mes "["+token(0)+"]"+" "+len(0)+"文字"
repeat ntoken
mes "("+token(cnt+1)+")"+" "+len(cnt+1)+"文字"
loop
stop
|
|
構文解析 v0.2 ( りさ )
日本語・コメント・命令の判定をしたい
テキストファイルから読み込みたい
+
| | 命令解析
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
| #module
#defcfunc DelSpace var p1, var p2
prmlen = strlen( p2 )
index = 0
last = 0
repeat prmlen
if peek( p2, cnt ) != ' ' { index = cnt :break }
loop
repeat prmlen-index
if peek( p2, prmlen-cnt-1 ) != ' ' { last = prmlen-cnt :break }
loop
prmsize = last-index
p1 = strmid( p2, index, prmsize )
return prmsize
#global
#define SCENARIO_NAME "scenario.txt"
#define SCENARIO_MAX 32000
#define SENTENCE_LEN 256 #define TOKEN_LEN 128 #define TOKEN_ELE 32
exist SCENARIO_NAME
c_size = strsize
if c_size = -1 { mes "!! scenario file doesn't exist !!" :stop }
if c_size > SCENARIO_MAX { mes "!! scenario file is too large !!" :stop }
sdim scenario, c_size
c_i = 0
bload SCENARIO_NAME, scenario
repeat -1
sdim sentence, SENTENCE_LEN
sdim token, TOKEN_LEN, TOKEN_ELE
dim len, TOKEN_ELE
n = 0
i = -1
ntoken = 0
if c_size = c_i :break
getstr sentence, scenario, c_i
c_i += strsize
n = strlen( sentence )
repeat n
if ( peek( sentence, cnt ) != ' ' ) { i = cnt :break }
loop
if wpeek( sentence, i ) = 0x2F2F :continue
if peek( sentence, i ) >= 0x80 { mes sentence :continue }
if i = -1 { mes "" :continue }
getstr string, sentence, i, '='
i += strsize
len(0) = DelSpace( token(0), string)
if len(0) = 0 { mes "命令が存在しません" :stop }
repeat TOKEN_ELE
if n = i { ntoken = cnt :break }
getstr string, sentence, i, ','
i+ =strsize
len(cnt+1) = DelSpace( token( cnt+1 ), string )
if len(cnt+1) = 0 :token( cnt+1 )="-1"
loop
mes "["+sentence+"]"
mes "トークンの数: "+ntoken
mes
mes "["+token(0)+"]"+" "+len(0)+"文字"
repeat ntoken
mes "("+token(cnt+1)+")"+" "+len(cnt+1)+"文字"
loop
loop
stop
|
|
htmlっぽい構文解析 ( まな )
<〜>間の文字列の処理をモジュール化してみました。
+
| | 命令解析
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
-
|
|
|
|
|
!
|
#module
#defcfunc GetHTML_BLK str p1,int p2,int p3
base = p1
start= p2
mode = p3
sdim RESULT
sdim SUB_RESULT
FLAG = 0
I=0
S = instr(base,start,"<") if S=-1: return "none"
FLAG = start+S
E = instr(base,FLAG,">") if E=-1: return "none"
SUB_RESULT=strmid(base,FLAG,E+1)
if mode=1{ repeat
I = peek(SUB_RESULT,cnt+1)
if (I=' ')|(I='>'):break
poke RESULT,cnt,I
loop
}
if mode=0 : RESULT = SUB_RESULT
return RESULT
#defcfunc GetHTML_PRM str p1,str p2
base = p1
object=p2
FLAG = 0 OK = 0
RESULT=""
repeat
I = instr(base, FLAG, " ") if I=-1 : return "none" FLAG+=I
CMD=""
repeat C = peek(base,FLAG+cnt+1) if (C='=')|(C='>')|(C=' ') : break poke CMD,cnt,C
loop
if CMD = object : OK=1 :break FLAG++
loop
if OK=0:return "none"
PARAMStart = instr(base,FLAG,"\"") PARAMEnd = instr(base,FLAG+PARAMStart+1,"\"")
RESULT = strmid(base,FLAG+PARAMStart+1,PARAMEnd)
return RESULT
#global
TEXT = "実験<img src=\"hogehoge.png\" alt=\"ほげ\">"
START=0
CMDBLOCK = GetHTML_BLK(TEXT,START,0)
CMDNAME = GetHTML_BLK(TEXT,START,1)
PRMNAME = GetHTML_PRM(TEXT,"src")
mes "ブロック:"+CMDBLOCK+""
mes "命令名 :"+CMDNAME+""
mes "SRC :"+PRMNAME+""
|
|
いろいろやり方があるだろうと思うので、今後の課題?も含めちょっと考えてみましょう。
とりあえず思いつくままに…。
…考えてたら頭痛くなってきた。w
スクリプトの種類をあげてみましょう。
次のような感じでしょうか。
- HTMLのようなタグで囲むタイプ
- 昔のBASICのような1行1命令
- CやHSPのような、1行1命令や複数行を囲む命令
どれがAVG向きなんでしょう…?
AVGを開発するのには、プログラマーである必要はありません。
あるいはパソコン初心者かもしれません。
出来る限り簡単で分かり易い書式を目指すと、やはりBASICのようなタイプに行き着くのではないでしょうか。
慣れた人の為にHTML形式も用意したいと思います。
どちらが便利かは・・・命令のタイプにもよると思います。
無駄なスペースやタブ、コメントなどを削除するタイミングはいつがいいのでしょうか。
- スクリプト初回読み込み時に、全体のエラーチェックを行なうと同時に余計なスペースなどを取り除く。(HSPがこれ?)
- 1つ命令を読み込むたびに余計なスペースなどを取り除く。
ことき前者の方法ではついでに解析しやすい書式に変えてしまうことがあります。こうしておくと実行時の処理が楽になりますし、若干の速度アップもできるかもですね。
前者は後者がある程度できるようにならないと難しいかもしれませんね。
命令の引数に、セリフなどで使用する文字列を記述する場合があります。
通常「"(ダブルクォーテーション)」で囲まれて記述されてます。
- 「"」で囲まれた範囲内はスペースを削除しない。(構文解析のときに間違って削除しない)
- 改行、「"」の記述方法、読み取り方法。
- \" → "、\\ → \、\n → 改行
※\を処理するときに、"表"や"ソ"など2バイト目が\になっているものを考慮しないとはまる
- その他特殊な文字の記述方法、読み取り方法。(タブとかハートなど…あんまりAVGでは必要性は感じませんが一応書いてみた。)
記述は、CやHSPの記述方法をまねすればほぼOK。
読み取りは…「"」〜「"」の間はできるだけそのままになるように地道に処理すればよさそう。
HSPで言うところのifやrepeat命令。
ifは1行記述にも出来ますが、複数行記述できたほうが処理できる幅が広がります。
複数行にした場合、次の命令が何処にあるかはネスト処理も含めスクリプトエンジン側で処理できたほうがよさそうです。(なんとなく)
- 一度、目指している形式で テスト シナリオを簡単に書いてみるのはどうでしょうか? -- osakana
- プログラムの技術とは関係ないですが、シナリオの規格を大雑把に決めた方がみんなでいろいろし易いですね。 -- 男性A?
- みなさん、こちらにいらしたのですネ☆ -- りさ
- 「メイン」みたいなものを書きたいんですが、どこに書けばいいんでしょう?構文解析の中はマズイですよね? -- りさ
- 『りさAVGエンジン』ってなページをソフト開発のトコに作って進めてはどうですか? -- kz3
- あっと...もしかして今りささん編集中だったかしら...変なラベルがぽつんとあったり、無駄な改行(~)をとったりしていました。あとラベル(*)の不具合とか...。 -- kz3
- wiki使えるか微妙ですけど、トライしてみますね!>kz3さん -- りさ
- いえ、大丈夫です☆ HTMLより難しいですねw >ありがとうございます。>不具合とか -- りさ
- はじめまして。突然すみません、まなさんのhtmlっぽい構文解析のモジュールを使わせていただいてもよろしいでしょうか? -- Pine?
- 探しても投稿されたコンテンツに関する著作権の記載が見つけられませんでしたが、wiki利用者は概ね より多くの人に利用して貰おうという意図で内容を公開していると思うので、自由に使っても大丈夫だと思いますよ。 -- osakana