Skin: [NORMAL]
[BLUE]
[DOS] [LIGHT]  / コピーするための表示 / 実行
このファイル: /home/web6047/www/cgi-bin/prj/20190503-javascript/vsync - snapshot 20190602/Scr.js
1 console.log( "Scr.js loading.." );
2 /* このコメントの最終更新日:19/05/20(月) 20:04:55
3
4 Scr.js
5
6 導入手順:
7
8 対象HTMLに、以下を挿入。
9 <script src="Scr.js"></script>
10
11 onload時の実行などで以下を実行。
12 scr = new Scr( function() {
13 alert( "Hello world!" );
14 } );
15 scr.exec();
16
17 すると、Hello world! とアラートが出る。
18
19
20 対応内容:
21
22 19/05/28(火) 22:17:39 if() 単文 に対応した。
23 19/05/22(水) 21:03:36 else if に対応した。
24 19/05/20(月) 20:52:21 if else に対応した。
25 19/05/19(日) 17:43:59 .js外部ファイル化
26 19/05/19(日) 17:43:02 関数ステップイン対応
27 関数呼び出し時、関数名の後に _ を付けると、
28 _ を付けていない関数をScrで実行する。
29 19/05/19(日) 17:40:16 関数スコープ、ブロックスコープ対応
30 19/05/19(日) 17:39:58 delay()関数作成
31 19/05/19(日) 17:39:35 if, while, for対応
32
33
34 非対応:
35
36 19/05/22(水) 21:59:23 break, continue
37 19/05/22(水) 21:59:02 for in
38 19/05/22(水) 21:58:36 do..while
39 19/05/22(水) 21:58:03 switch
40 19/05/22(水) 21:55:43 do..until
41 */
42
43 var cl = console.log;
44 var HereDocument = /[^]*\/\*\s([^]*)\*\/\}$/;
45 var FunctionCodeRE = /[^]*?\{\s([^]*)\}$/;
46
47 first = true;
48
49 //コンストラクタ
50 function Scr( arg ) {
51
52 /*
53 現在
54 if( 改行 ) に非対応
55 function() {} に非対応
56 if() func(); に非対応
57 */
58
59 //check. 引数の種類によって初期化方法を分ける
60 if( typeof arg === "object" ) {
61
62 //objectが指定されたとき(制御文のブロックの実行のために呼ばれた場合)
63 text = arg.block;
64 this.selfBlock = arg;
65 this.blockScope = new Object();
66 this.functionScope = Scr.functionScopeStack[ Scr.functionScopeStack.length - 1 ];
67
68 } else {
69
70 //objectではないとき(メインプログラムの場合)
71 if( typeof arg === "function" )
72 text = arg.toString().match( FunctionCodeRE )[ 1 ];
73 else
74 text = arg;
75
76 this.selfBlock = {
77 typ : "main",
78 ini : null,
79 cnd : null,
80 add : null,
81 };
82 this.blockScope = new Object();
83 this.functionScope = this.blockScope;
84 Scr.functionScopeStack.push( this.blockScope );
85 }
86
87 //textを整形
88
89 //セミコロンで改行したいので、for文を退避
90 var forEscapes;
91 if( forEscapes = text.match( /for\([\s\S]*?;[\s\S]*?;[\s\S]*?\)/g ) ) {
92 text = text.replace( /for\([\s\S]*?;[\s\S]*?;[\s\S]*?\)/g, "_forEscape_" );
93 }
94
95 //セミコロンで改行
96 text = text.replace( /;/g, ";\n" );
97
98 //退避したfor文を復帰
99 if( forEscapes )
100 for( var i = 0; i < forEscapes.length; i++ ) {
101 text = text.replace( /_forEscape_/, forEscapes[ i ] );
102 }
103
104 //if() 単文 に対応
105 /*
106 if()の)を認知するとき、
107 適当に if\(.*?\) にしてしまうと、()内の関数呼び出しなどで認知が不正になる。
108 正しく認知させるために以下のように大きめになった。
109 */
110 var tail = text;
111 text = "";
112 while( tail.match( /\b(if\(|else\s|while\(|for\()/ ) ) {
113 text += RegExp.leftContext + RegExp.lastMatch;
114 tail = RegExp.rightContext;
115
116 //check. ifやwhileなどは、条件部分をスキップする
117 var isElse = RegExp.lastMatch.match( /^else\s$/ );
118
119 //check. elseで、else ifの場合はやりなおしてifを検知させる
120 if( isElse && tail.match( /^\s*if\(/ ) ) continue;
121
122 if( ! isElse ) {
123 //ex. if( 'res[ 1 ]' ) 'res[ 2 ]'
124 var res = Scr.getToPairBracket( tail );
125 text += res[ 1 ] + ")";
126 tail = res[ 2 ].substr( 1 );
127 }
128
129 //ifやwhileに続く{}なしの命令文単文を{}付きに変換
130 if( tail.match( /^\s*[^{\s].*;/ ) ) {
131 tail = RegExp.rightContext;
132 text += "{" + RegExp.lastMatch + "}";
133 }
134 }
135 text += tail;
136
137 text = text.replace( /[{}]/g, function( s ) { return "\n" + s + "\n" } );
138 text = text.replace( /^\s+/mg, "" );
139 text = text.replace( /\/\/.*$/gm, "" );
140 text = text.replace( /\n$/, "" ); //最終行の改行除去
141
142 //debug
143 if( first ) {
144 first = false;
145 cl( "\n=====" + text.replace( /^/mg, "\t" ) + "\n====\n" );
146 }
147
148 this.lines = text.split( /\n/ );
149
150 //全体制御用
151 this.programCounter = 0;
152 this.isEnd = false;
153 this.isDelayed = false;
154
155 //制御構文用
156 this.subBlockLV = -1;
157 this.subBlock = null;
158 this.resultOfIf = null;
159 this.isGettingSubBlock = false;
160
161 }//class
162 Scr.getToPairBracket = function( string ) {
163 var result = [
164 string, //もともとの文字列
165 "", //対応する閉じ括弧までの文字列(閉じ括弧を含まない)
166 "" //対応する閉じ括弧以降の文字列(閉じ括弧を含む)
167 ];
168 var tail = string;
169 var level = 0;
170 while( tail.match( /[\(\)]/ ) ) {
171 result[ 1 ] += RegExp.leftContext;
172 tail = RegExp.rightContext;
173 var got = RegExp.lastMatch;
174 //check.
175 if( got == "(" ) {
176 level++;
177 } else {
178 //got == ")"
179 level--;
180 //check.
181 if( level == -1 ) {
182 result[ 2 ] = got + tail;
183 return result;
184 }
185 }
186 result[ 1 ] += got;
187 }
188 return result;
189 }
190 Scr.prototype.execBlock = function( subBlock ) {
191 //現在のプログラムを上位のブロックとして保管
192 Scr.stack.push( this );
193 //subBlockを下位ブロックとして実行
194 scr = new Scr( subBlock );
195 //※scrはグローバル
196 setTimeout( scr.exec.bind( scr ), 0 );
197 }
198 Scr.prototype.eval = function( script ) {
199 with( this.functionScope )
200 with( this.blockScope )
201 return eval( script );
202 }
203 Scr.prototype.exec = function() {
204 /* 19/05/19(日) 17:47:01
205 execメソッド:
206
207 大きなwhileループがあり、そのあとに、ループをどのように抜けたかというチェックが2つ続いている。
208
209 大きなwhileループは、基本的にプログラムの行を1行ずつ進める。
210 大きなwhileループは、おおまかに3つの部分、
211 プログラムカウンタを進める部分、
212 if, while, for文等制御構文のブロック部分を取得する部分
213 if, while, for文等制御構文や平文を識別する部分
214 で構成されている。
215 大きなwhileループのあとに続く、ループをどのように抜けたかという2つのチェックは
216 プログラムの終了でループを抜けた場合
217 delay命令でループを抜けた場合
218 の2つであり、
219 そのうちの1つ、プログラムの終了でループを抜けた場合は、
220 ブロックが最後に到達し、ブロックから出る処理である。
221 (stackに保管した親ブロックを取り出すというもの)
222 もう1つの、delay命令でループを抜けた場合は、
223 setTimeoutを使って100ms後に復帰するという処理である。
224
225 execメソッドを実行すると、Scrが持つプログラムのソースコードをすべて実行することになる。
226 */
227
228 while( ! this.isEnd ) {
229
230 //check. 直前のif文が偽だったとき次のelseは有効。
231 var isEnableElse = this.resultOfIf == false;
232 this.resultOfIf = null;
233
234 var line = this.lines[ this.programCounter ];
235
236 //======プログラムカウンタを進める======
237 this.programCounter ++;
238
239 //check. プログラム(ブロック)の末尾を超えた
240 if( this.programCounter > this.lines.length ) {
241 //終了、またはループ処理
242 switch( this.selfBlock.typ ) {
243 case "main":
244 case "if":
245 case "else":
246 case "else if":
247 //プログラム(ブロック)の終了
248 this.isEnd = true;
249 break;
250 case "for":
251 //for文 カウンタ変数の更新
252 if( this.selfBlock.add ) this.eval( this.selfBlock.add );
253 //breakせず下へつづく
254 case "while":
255 //ループの継続条件を評価(while,for共通 ループとして2回目以降)
256 if( this.eval( this.selfBlock.cnd ) ) {
257 //ループ継続
258 this.programCounter = 0;
259 } else {
260 //ループ終了
261 this.isEnd = true;
262 }
263 break;
264 }
265 continue;
266 }//check.
267
268
269 //======if, while, for文等制御構文のブロック部分を取得する======
270
271 if( this.isGettingSubBlock ) {
272 if( line == "{" ) { //ブロック開始文字(入れ子の開始)
273 this.subBlockLV ++;
274 } else if( line == "}" ) { //ブロック終了文字
275 this.subBlockLV --;
276 //check. ブロック取得完了か
277 if( this.subBlockLV == 0 ) {
278 this.isGettingSubBlock = false;
279
280 //for文 初期化実行
281 if( this.subBlock.typ == "for" ) this.eval( this.subBlock.ini );
282
283 //if,while,for共通 条件評価(ループとしては1回目となる)
284 var subBlockIsIf = this.subBlock.typ.match( /^(?:if|else if)$/ );
285 if( this.eval( this.subBlock.cnd ) ) {
286 //ブロック内を実行
287 if( subBlockIsIf ) this.resultOfIf = true;
288 this.execBlock( this.subBlock );
289 break; //このwhileループは一時終了する(subBlock実行終了で再開する)
290 } else {
291 //ブロック内をスキップ
292 if( subBlockIsIf ) this.resultOfIf = false;
293 continue; //次へ
294 }
295 }//if
296 }//if
297
298 //ブロック内の1行として取得
299 this.subBlock.block += line + "\n";
300 continue; //ブロック取得中は以上を繰り返す
301
302 }//if
303
304
305 //======if, while, for文等制御構文や平文を識別する部分======
306
307 if( line.match( /^delay\(/ ) ) {
308
309 //同期命令はループを抜ける
310 this.isDelayed = true;
311 break; //外側whileをbreak
312
313 } else if( line.match( /^(if|while)\(([\s\S]*)\)/ ) ) {
314
315 //if( cnd ), while( cnd )
316 this.subBlock = {
317 typ : RegExp.$1,
318 cnd : RegExp.$2,
319 }
320 continue; //次のループで、この下のほうの【ブロック開始文】が読まれるはず。
321
322 } else if( line.match( /^for\(\s*([\s\S]*)\s*;\s*([\s\S]*)\s*;\s*([\s\S]*\s*)\)/ ) ) {
323
324 //for( ini; cnd; add )
325 this.subBlock = {
326 typ : "for",
327 ini : RegExp.$1,
328 cnd : RegExp.$2,
329 add : RegExp.$3,
330 }
331
332 //check. for変数をグローバル変数にする
333 if( this.subBlock.ini.match( /^var / ) ) {
334 this.subBlock.ini = this.subBlock.ini.replace( /^var\s+/, "window." );
335 } else {
336 this.subBlock.ini = "window." + this.subBlock.ini;
337 }
338 this.subBlock.cnd = "window." + this.subBlock.cnd;
339 this.subBlock.add = "window." + this.subBlock.add;
340
341 continue; //次のループで、この下のほうの【ブロック開始文】が読まれるはず。
342 } else if( line.match( /^else\s+if\(([\s\S]*)\)/ ) ) {
343
344 //else if( cnd )
345 this.subBlock = {
346 typ : "else if",
347 cnd : isEnableElse.toString() + " && ( " + RegExp.$1 + ")",
348 }
349 continue; //次のループで、この下のほうの【ブロック開始文】が読まれるはず。
350
351 } else if( line.match( /^else\s*$/ ) ) {
352
353 //else
354 this.subBlock = {
355 typ : "else",
356 cnd : isEnableElse.toString(),
357 }
358 continue; //次のループで、この下のほうの【ブロック開始文】が読まれるはず。
359
360 } else if( line == "{" ) {
361
362 //【ブロック開始文】
363 this.subBlock.block = "";
364 this.subBlockLV = 1;
365 this.isGettingSubBlock = true;
366 continue; //次のループから、ブロックの取得が始まる。
367
368 } else {
369
370 //平文(実行する)
371
372 //平文内の関数を実行する
373 line = line.replace( /;$/, "" ); //末尾;除去
374 if( line.match( /^([a-z0-9$_\.]*_)\(([\s\S]*)\)$/ ) ) {
375
376 //関数名末尾に _ が付与されている場合
377 //関数の内容へステップインする
378 var funcName = RegExp.$1;
379 var funcOpt = RegExp.$2;
380 //check. 関数名末尾の _ を除去
381 funcName = funcName.replace( /_$/, "" );
382 cl( funcName );
383 var source = window[ funcName ].toString().match( FunctionCodeRE )[ 1 ];
384 this.execBlock( source );
385 break; //この処理(親プロセス)を一時的に終了へ
386
387 } else {
388
389 //関数定義
390 var recursive = function( string ) {
391 var right = string;
392 string = "";
393 //関数にマッチするあいだ
394 while( right.match( /([a-z0-9$_\.]*)\(([\s\S]*)\)/ ) ) {
395 var left = RegExp.leftContext;
396 right = RegExp.rightContext;
397 var center = RegExp.lastMatch;
398 var funcName = RegExp.$1;
399 var funcOpt = RegExp.$2;
400 //関数の引数部分について再帰処理
401 string += left + this.eval( funcName + "(" + recursive( funcOpt ) + ")" );
402 }
403 string += right;
404 return string;
405 }.bind( this );
406
407 var line2 = recursive( line );
408 line2 = line2.replace( /^var\s+/, "scr.functionScope." );
409 line2 = line2.replace( /^let\s+/, "scr.blockScope." );
410
411 this.eval( line2 );
412
413 }
414 } //if
415 }//while
416
417 //プログラム(ブロック)の終了でループを抜けた場合
418 if( this.isEnd ) {
419 if( Scr.stack.length > 0 ) {
420 //保管していた上位のブロックを取り出す
421 scr = Scr.stack.pop();
422 setTimeout( scr.exec.bind( scr ), 0 );
423 } else {
424 console.log( "all end." );
425 }
426 return;
427 }
428
429 //delay命令でループを抜けた場合
430 if( this.isDelayed ) {
431 this.isDelayed = false;
432 this.timerID = setTimeout( function() { this.exec(); }.bind( this ), 100 );
433 return;
434 }
435
436 };//Scr.prototype.exec
437
438
439 //クラス変数
440
441 Scr.stack = new Array();
442 Scr.functionScopeStack = new Array();