nadesiko3 3.7.21 → 3.7.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/batch/command.txt +370 -334
  2. package/batch/jsplugin2text.nako3 +1 -1
  3. package/batch/pickup_command.nako3 +12 -0
  4. package/core/command/snako.mts +5 -1
  5. package/core/deno/snako.ts +5 -1
  6. package/core/package-lock.json +382 -21
  7. package/core/package.json +4 -4
  8. package/core/src/nako3.mts +178 -614
  9. package/core/src/nako_basic_plugins.mts +39 -0
  10. package/core/src/nako_core_version.mts +2 -2
  11. package/core/src/nako_csv.mts +0 -1
  12. package/core/src/nako_event.mts +49 -0
  13. package/core/src/nako_gen.mts +291 -202
  14. package/core/src/nako_indent_inline.mts +2 -2
  15. package/core/src/nako_lex_rules.mts +1 -1
  16. package/core/src/nako_parser3.mts +106 -165
  17. package/core/src/nako_parser_async.mts +97 -0
  18. package/core/src/nako_parser_base.mts +4 -55
  19. package/core/src/nako_parser_message.mts +105 -0
  20. package/core/src/nako_parser_operator.mts +93 -0
  21. package/core/src/nako_plugin_manager.mts +260 -0
  22. package/core/src/nako_require.mts +292 -0
  23. package/core/src/nako_runner.mts +191 -0
  24. package/core/src/nako_tokenizer.mts +221 -0
  25. package/core/src/plugin_csv.mts +1 -1
  26. package/core/src/plugin_system.mts +34 -3259
  27. package/core/src/plugin_system_array.mts +699 -0
  28. package/core/src/plugin_system_datetime.mts +368 -0
  29. package/core/src/plugin_system_debug.mts +403 -0
  30. package/core/src/plugin_system_dict.mts +85 -0
  31. package/core/src/plugin_system_json.mts +73 -0
  32. package/core/src/plugin_system_math.mts +383 -0
  33. package/core/src/plugin_system_regexp.mts +120 -0
  34. package/core/src/plugin_system_stdio.mts +86 -0
  35. package/core/src/plugin_system_string.mts +666 -0
  36. package/core/src/plugin_system_timer.mts +152 -0
  37. package/core/src/plugin_system_types.mts +151 -0
  38. package/core/src/plugin_system_url.mts +193 -0
  39. package/core/src/plugin_toml.mts +3 -3
  40. package/core/test/fixtures/README.md +39 -0
  41. package/core/test/fixtures/make_parser_ast_golden.mjs +31 -0
  42. package/core/test/fixtures/parser_ast_golden.json +8027 -0
  43. package/core/test/fixtures/parser_corpus.mjs +120 -0
  44. package/core/test/indent_test.mjs +6 -0
  45. package/core/test/nako_basic_plugins_test.mjs +44 -0
  46. package/core/test/nako_event_test.mjs +85 -0
  47. package/core/test/nako_gen_perf_test.mjs +178 -0
  48. package/core/test/nako_parser_async_test.mjs +112 -0
  49. package/core/test/nako_parser_test.mjs +160 -0
  50. package/core/test/nako_plugin_manager_test.mjs +185 -0
  51. package/core/test/nako_require_test.mjs +153 -0
  52. package/core/test/nako_runner_test.mjs +114 -0
  53. package/core/test/nako_tokenizer_test.mjs +115 -0
  54. package/core/test/plugin_system_debug_test.mjs +141 -0
  55. package/core/test/plugin_system_split_test.mjs +179 -0
  56. package/core/test/plugin_system_test.mjs +6 -0
  57. package/core/tsconfig.json +0 -1
  58. package/package.json +17 -21
  59. package/release/_hash.txt +36 -36
  60. package/release/_script-tags.txt +16 -16
  61. package/release/command.json +1 -1
  62. package/release/command.json.js +1 -1
  63. package/release/command_cnako3.json +1 -1
  64. package/release/command_list.json +1 -1
  65. package/release/edit_main.js +6 -6
  66. package/release/edit_main.js.map +3 -3
  67. package/release/editor.js +6 -6
  68. package/release/plugin_keigo.js.map +3 -3
  69. package/release/plugin_markup.js +46 -46
  70. package/release/plugin_markup.js.map +3 -3
  71. package/release/plugin_weykturtle3d.js +1 -1
  72. package/release/plugin_weykturtle3d.js.map +3 -3
  73. package/release/version.js +2 -2
  74. package/release/version_main.js +2 -2
  75. package/release/version_main.js.map +1 -1
  76. package/release/wnako3.js +186 -213
  77. package/release/wnako3.js.map +4 -4
  78. package/release/wnako3webworker.js +172 -199
  79. package/release/wnako3webworker.js.map +4 -4
  80. package/src/cnako3mod.mjs +7 -2
  81. package/src/cnako3mod.mts +7 -2
  82. package/src/nako_version.mjs +2 -2
  83. package/src/nako_version.mts +2 -2
  84. package/src/plugin_browser_ajax.mjs +4 -4
  85. package/src/plugin_browser_ajax.mts +4 -4
  86. package/src/plugin_browser_audio.mjs +10 -10
  87. package/src/plugin_browser_audio.mts +10 -10
  88. package/src/plugin_browser_camera.mjs +4 -4
  89. package/src/plugin_browser_camera.mts +4 -4
  90. package/src/plugin_browser_canvas.mjs +2 -2
  91. package/src/plugin_browser_canvas.mts +2 -2
  92. package/src/plugin_browser_crypto.mjs +3 -3
  93. package/src/plugin_browser_crypto.mts +3 -3
  94. package/src/plugin_browser_dom_event.mjs +1 -1
  95. package/src/plugin_browser_dom_event.mts +1 -1
  96. package/src/plugin_browser_geolocation.mjs +1 -1
  97. package/src/plugin_browser_geolocation.mts +1 -1
  98. package/src/plugin_browser_hotkey.mjs +1 -1
  99. package/src/plugin_browser_hotkey.mts +1 -1
  100. package/src/plugin_browser_html.mjs +1 -1
  101. package/src/plugin_browser_html.mts +1 -1
  102. package/src/plugin_browser_location.mjs +1 -1
  103. package/src/plugin_browser_location.mts +1 -1
  104. package/src/plugin_browser_speech.mjs +1 -1
  105. package/src/plugin_browser_speech.mts +1 -1
  106. package/src/plugin_browser_storage.mjs +2 -2
  107. package/src/plugin_browser_storage.mts +2 -2
  108. package/src/plugin_httpserver.mjs +3 -3
  109. package/src/plugin_httpserver.mts +3 -3
  110. package/src/plugin_keigo.mjs +2 -2
  111. package/src/plugin_keigo.mts +2 -2
  112. package/src/plugin_node.mjs +48 -48
  113. package/src/plugin_node.mts +53 -53
  114. package/src/plugin_weykturtle3d.mjs +56 -56
  115. package/src/plugin_weykturtle3d.mts +56 -56
  116. package/src/wnako3.mjs +1 -1
  117. package/src/wnako3.mts +1 -1
  118. package/src/wnako3_editor.mjs +5 -5
  119. package/src/wnako3_editor.mts +5 -5
  120. package/src/wnako3mod.mjs +2 -2
  121. package/src/wnako3mod.mts +2 -2
  122. package/core/src/nako3.mjs +0 -1021
  123. package/core/src/nako_ast.mjs +0 -4
  124. package/core/src/nako_colors.mjs +0 -77
  125. package/core/src/nako_core_version.mjs +0 -8
  126. package/core/src/nako_csv.mjs +0 -193
  127. package/core/src/nako_errors.mjs +0 -166
  128. package/core/src/nako_from_dncl.mjs +0 -285
  129. package/core/src/nako_from_dncl2.mjs +0 -347
  130. package/core/src/nako_gen.mjs +0 -2500
  131. package/core/src/nako_global.mjs +0 -138
  132. package/core/src/nako_indent.mjs +0 -442
  133. package/core/src/nako_indent_chars.mjs +0 -29
  134. package/core/src/nako_indent_inline.mjs +0 -361
  135. package/core/src/nako_josi_list.mjs +0 -47
  136. package/core/src/nako_lex_rules.mjs +0 -319
  137. package/core/src/nako_lexer.mjs +0 -794
  138. package/core/src/nako_logger.mjs +0 -221
  139. package/core/src/nako_parser3.mjs +0 -3250
  140. package/core/src/nako_parser_base.mjs +0 -403
  141. package/core/src/nako_parser_const.mjs +0 -37
  142. package/core/src/nako_prepare.mjs +0 -329
  143. package/core/src/nako_reserved_words.mjs +0 -42
  144. package/core/src/nako_source_mapping.mjs +0 -207
  145. package/core/src/nako_test.mjs +0 -37
  146. package/core/src/nako_token.mjs +0 -1
  147. package/core/src/nako_tools.mjs +0 -53
  148. package/core/src/nako_types.mjs +0 -14
  149. package/core/src/plugin_api.mjs +0 -4
  150. package/core/src/plugin_csv.mjs +0 -97
  151. package/core/src/plugin_math.mjs +0 -352
  152. package/core/src/plugin_promise.mjs +0 -102
  153. package/core/src/plugin_system.mjs +0 -3810
  154. package/core/src/plugin_test.mjs +0 -52
  155. package/core/src/plugin_toml.mjs +0 -39
@@ -0,0 +1,120 @@
1
+ /* eslint-disable no-undef */
2
+ /**
3
+ * 構文解析器(NakoParser)の回帰テスト用コーパス (#2364)
4
+ *
5
+ * `core/test/nako_parser_test.mjs`(検証)と
6
+ * `core/test/fixtures/make_parser_ast_golden.mjs`(ゴールデン生成)の
7
+ * 双方から参照する。テスト本体とは分離しておかないと、
8
+ * 生成スクリプトを実行したときにテストまで走ってしまう。
9
+ *
10
+ * このディレクトリは `core/test/*.mjs` のテストグロブに含まれないので、
11
+ * テスト実行時にこのファイル自体が走ることはない。
12
+ */
13
+ import { NakoCompiler } from '../../src/nako3.mjs'
14
+
15
+ /** ゴールデン比較で使うファイル名(AST の file フィールドに入るので固定する) */
16
+ export const CORPUS_FILENAME = 'main.nako3'
17
+
18
+ /**
19
+ * 構文解析のコーパス。
20
+ * `core/src/nako_parser3.mts` の構文規則クラスタを一通り踏むように選んである。
21
+ */
22
+ export const PARSER_CORPUS = {
23
+ // --- 文の骨格 (parse / ySentenceList / ySentence / yBlock / yEOL) ---
24
+ '文-空行とコメント': '# コメント\n\nA=1\n',
25
+ '文-複数文': 'A=1\nB=2\nA+Bを表示\n',
26
+ '文-連文': '1に2を足して3を掛けて表示\n',
27
+ '文-抜ける': '3回\n抜ける\nここまで\n',
28
+ '文-続ける': '3回\n続ける\nここまで\n',
29
+ '文-ここから': '3回ここから\nA=1\nここまで\n',
30
+ '文-ハテナ表示': '??1+2\n',
31
+
32
+ // --- モード設定・実行モード指定 (ySpeedMode / yPerformanceMonitor / yDNCLMode ほか) ---
33
+ 'モード-DNCL': '!DNCLモード\nA=1\n',
34
+ 'モード-速度優先': '「全て」で実行速度優先\nA=1\nここまで\n',
35
+ 'モード-モニタ': '「全て」でパフォーマンスモニタ適用\nA=1\nここまで\n',
36
+ 'モード-厳チェック': '!厳チェック\nA=1\n',
37
+ 'モード-公開既定値': '!モジュール公開既定値=「非公開」\nA=1\n',
38
+
39
+ // --- 関数定義 (yDefFunc / yDefFuncCommon / yMumeiFunc / yDefTest) ---
40
+ '定義-関数': '●(AとBを)足すとは\nA+Bで戻る\nここまで\n',
41
+ '定義-関数公開': '●{公開}(Aを)倍とは\nA*2で戻る\nここまで\n',
42
+ '定義-カッコ引数': '●足す(AとBを)とは\nA+Bで戻る\nここまで\n',
43
+ '定義-ここから': '●Fとは、ここから\nA=1\nここまで\n',
44
+ '定義-無名関数': 'F=関数(Aを)\nAを表示\nここまで\n',
45
+ '定義-テスト': '●テスト:足すとは\nA=1\nここまで\n',
46
+
47
+ // --- 制御構文 (yIF / yFor / yWhile / ySwitch / yTryExcept ほか) ---
48
+ '制御-もし': 'もしA=1ならば\nB=1\n違えば\nB=2\nここまで\n',
49
+ '制御-もし単文': 'もしA=1ならばB=1\n',
50
+ '制御-回': '3回\nA=1\nここまで\n',
51
+ '制御-間': 'A<3の間\nA=A+1\nここまで\n',
52
+ '制御-後判定': '後判定\nA=A+1\nここまでA>3になるまで\n',
53
+ '制御-繰返': 'Nを1から10まで繰り返す\nNを表示\nここまで\n',
54
+ '制御-増繰返': 'Nを1から10まで2ずつ増やして繰り返す\nNを表示\nここまで\n',
55
+ '制御-減繰返': 'Nを10から1まで1ずつ減らして繰り返す\nNを表示\nここまで\n',
56
+ '制御-反復': '[1,2,3]を反復\n対象を表示\nここまで\n',
57
+ '制御-条件分岐': 'Aで条件分岐\n1ならば\nB=1\nここまで\n違えば\nB=2\nここまで\nここまで\n',
58
+ '制御-戻る': '●Fとは\n1で戻る\nここまで\n',
59
+ '制御-エラー監視': 'エラー監視\nA=1\nエラーならば\nB=2\nここまで\n',
60
+
61
+ // --- 代入・変数定義 (yLet / yDainyu / ySadameru / yIncDec) ---
62
+ '代入-単純': 'A=1\n',
63
+ '代入-代入文': 'Aに1を代入\n',
64
+ '代入-定める': 'Aを1に定める\n',
65
+ '代入-変数宣言': '変数 A=1\n',
66
+ '代入-定数宣言': '定数 A=1\n',
67
+ '代入-とは変数': 'Aとは変数=1\n',
68
+ '代入-複数変数': '変数 [A,B]=[1,2]\n',
69
+ '代入-複数代入': 'A,B=[1,2]\n',
70
+ '代入-増やす': 'Aを1だけ増やす\n',
71
+ '代入-減らす': 'Aを1だけ減らす\n',
72
+ '代入-プロパティ': 'A={}\nA$「b」=1\n',
73
+
74
+ // --- 配列要素への代入 (yLetArrayAt / yLetArrayBracket) ---
75
+ '配列-角括弧': 'A=[0]\nA[0]=1\n',
76
+ '配列-アット': 'A=[0]\nA@0=1\n',
77
+ '配列-二次元': 'A=[[0]]\nA[0][1]=1\n',
78
+ '配列-カンマ': 'A=[[0]]\nA[0,1]=1\n',
79
+
80
+ // --- 関数呼び出し (yCall / yCallFunc / yGetArgParen) ---
81
+ '呼出-単純': '「あ」を表示\n',
82
+ '呼出-C風': '足す(1,2)を表示\n',
83
+ '呼出-一語関数': 'A=1\nAを表示\n',
84
+
85
+ // --- 式・値 (yCalc / yValue / yValueKakko / yValueWord) ---
86
+ '式-カッコ': '(1+2)*3を表示\n',
87
+ '式-演算子': '1+2*3-4/5を表示\n',
88
+ '式-比較': 'もし1<2かつ3>2ならばA=1\n',
89
+ '式-範囲': 'A=1…10\n',
90
+ '式-単項マイナス': 'A=1\nB=-A\n',
91
+ '式-NOT': 'A=1\nB=!A\n',
92
+
93
+ // --- JSON/配列リテラル (yJSONArray / yJSONObject / yRefArrayValue) ---
94
+ 'リテラル-配列': 'A=[1,2,3]\n',
95
+ 'リテラル-辞書': 'A={"a":1,"b":2}\n',
96
+ 'リテラル-辞書省略': 'A={a:1}\n',
97
+ 'リテラル-空配列': 'A=[]\n',
98
+ 'リテラル-空辞書': 'A={}\n',
99
+ 'リテラル-入れ子': 'A=[[1,2],[3,4]]\n',
100
+ 'リテラル-添字直接': 'A=[[1,2],[3,4]][0][1]\n',
101
+ 'リテラル-アット直接': 'A=[1,2,3]@0\n',
102
+ 'リテラル-プロパティ直接': 'A={a:1}$「a」\n',
103
+
104
+ // --- 非同期関数の判定 (_checkAsyncFn) ---
105
+ '非同期-async関数': '●Fとは\n1秒待つ\nここまで\nF\n'
106
+ }
107
+
108
+ /**
109
+ * AST を JSON 化可能なプレーンオブジェクトに変換する。
110
+ * `meta.fn` にはプラグイン関数の実体が入るため、JSON.stringify が黙って捨てて
111
+ * しまわないよう replacer で目印に置き換える。
112
+ */
113
+ export function toPlainAst (ast) {
114
+ return JSON.parse(JSON.stringify(ast, (key, value) => (typeof value === 'function' ? '[fn]' : value)))
115
+ }
116
+
117
+ /** なでしこのコードを構文解析してプレーンな AST を返す */
118
+ export function parseToPlainAst (code) {
119
+ return toPlainAst(new NakoCompiler().parse(code, CORPUS_FILENAME))
120
+ }
@@ -292,4 +292,10 @@ describe('indent', async () => {
292
292
  '\t\t続ける\n' +
293
293
  '\tcを表示\n', '1\n0')
294
294
  })
295
+ it('useIndentSyntaxが正しく動作し、インデント構文が解釈されること (#2333)', async () => {
296
+ await cmp('!インデント構文\n' +
297
+ 'もし1=1ならば\n' +
298
+ '  「OK」と表示。\n', 'OK')
299
+ })
295
300
  })
301
+
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-undef */
2
+ import { describe, it } from 'node:test'
3
+ import assert from 'assert'
4
+
5
+ import { NakoCompiler } from '../src/nako3.mjs'
6
+ import { basicPlugins, registerBasicPlugins } from '../src/nako_basic_plugins.mjs'
7
+
8
+ /**
9
+ * 基本プラグインの一覧を NakoCompiler から分離したモジュールのテスト (#2360)
10
+ */
11
+ describe('nako_basic_plugins_test', () => {
12
+ it('基本プラグインの一覧を取得できる', () => {
13
+ assert.strictEqual(basicPlugins.length, 6)
14
+ const names = basicPlugins.map((po) => po.meta.value.pluginName)
15
+ assert.deepStrictEqual(names, [
16
+ 'plugin_system', 'plugin_math', 'plugin_promise', 'plugin_test', 'plugin_csv', 'plugin_toml'
17
+ ])
18
+ })
19
+
20
+ it('registerBasicPluginsは一覧の順にaddPluginを呼ぶ', () => {
21
+ const called = []
22
+ registerBasicPlugins({ addPlugin: (po) => { called.push(po) } })
23
+ assert.deepStrictEqual(called, basicPlugins)
24
+ })
25
+
26
+ it('useBasicPluginがtrueなら基本プラグインの命令を使える', () => {
27
+ const nako = new NakoCompiler({ useBasicPlugin: true })
28
+ assert.ok(nako.getFunc('表示') !== undefined, 'PluginSystemの命令')
29
+ assert.ok(nako.getFunc('SIN') !== undefined, 'PluginMathの命令')
30
+ })
31
+
32
+ it('useBasicPluginがfalseなら基本プラグインは登録されない', () => {
33
+ const nako = new NakoCompiler({ useBasicPlugin: false })
34
+ assert.strictEqual(nako.getFunc('表示'), undefined)
35
+ assert.deepStrictEqual(Object.keys(nako.getPluginfiles()), [])
36
+ })
37
+
38
+ it('addBasicPluginsを後から呼んでも登録できる', () => {
39
+ const nako = new NakoCompiler({ useBasicPlugin: false })
40
+ nako.addBasicPlugins()
41
+ assert.ok(nako.getFunc('表示') !== undefined)
42
+ assert.strictEqual(Object.keys(nako.getPluginfiles()).length, basicPlugins.length)
43
+ })
44
+ })
@@ -0,0 +1,85 @@
1
+ /* eslint-disable no-undef */
2
+ import { describe, it } from 'node:test'
3
+ import assert from 'assert'
4
+
5
+ import { NakoCompiler } from '../src/nako3.mjs'
6
+ import { NakoEventEmitter } from '../src/nako_event.mjs'
7
+
8
+ /**
9
+ * イベント機構を NakoCompiler から分離したモジュールのテスト (#2360)
10
+ */
11
+ describe('nako_event_test', () => {
12
+ it('onで登録したコールバックがfireで呼ばれる', () => {
13
+ const emitter = new NakoEventEmitter()
14
+ const log = []
15
+ emitter.on('beforeParse', (v) => { log.push(`a:${v}`) })
16
+ emitter.on('beforeParse', (v) => { log.push(`b:${v}`) })
17
+ emitter.fire('beforeParse', 1)
18
+ assert.deepStrictEqual(log, ['a:1', 'b:1'])
19
+ })
20
+
21
+ it('登録していないイベントを発火しても何も起きない', () => {
22
+ const emitter = new NakoEventEmitter()
23
+ let called = 0
24
+ emitter.on('beforeParse', () => { called++ })
25
+ emitter.fire('finish', null)
26
+ assert.strictEqual(called, 0)
27
+ })
28
+
29
+ it('別のイベント名のコールバックは呼ばれない', () => {
30
+ const emitter = new NakoEventEmitter()
31
+ const log = []
32
+ emitter.on('beforeParse', () => { log.push('parse') })
33
+ emitter.on('finish', () => { log.push('finish') })
34
+ emitter.fire('finish', null)
35
+ assert.deepStrictEqual(log, ['finish'])
36
+ })
37
+
38
+ it('getEventListで登録済みのイベントを取得できる', () => {
39
+ const emitter = new NakoEventEmitter()
40
+ emitter.on('beforeRun', () => {})
41
+ assert.strictEqual(emitter.getEventList().length, 1)
42
+ assert.strictEqual(emitter.getEventList()[0].eventName, 'beforeRun')
43
+ })
44
+
45
+ it('clearで登録済みのイベントを削除できる', () => {
46
+ const emitter = new NakoEventEmitter()
47
+ let called = 0
48
+ emitter.on('finish', () => { called++ })
49
+ emitter.clear()
50
+ emitter.fire('finish', null)
51
+ assert.strictEqual(called, 0)
52
+ assert.strictEqual(emitter.getEventList().length, 0)
53
+ })
54
+
55
+ it('NakoCompilerのaddListenerでイベントが発火する順番', async () => {
56
+ const nako = new NakoCompiler()
57
+ const log = []
58
+ nako.addListener('beforeParse', () => { log.push('beforeParse') })
59
+ nako.addListener('beforeGenerate', () => { log.push('beforeGenerate') })
60
+ nako.addListener('afterGenerate', () => { log.push('afterGenerate') })
61
+ nako.addListener('beforeRun', () => { log.push('beforeRun') })
62
+ nako.addListener('finish', () => { log.push('finish') })
63
+ await nako.runAsync('「テスト」を表示', 'main.nako3')
64
+ assert.deepStrictEqual(log, ['beforeParse', 'beforeGenerate', 'afterGenerate', 'beforeRun', 'finish'])
65
+ })
66
+
67
+ it('beforeParseにはソースコードが渡される', async () => {
68
+ const nako = new NakoCompiler()
69
+ let received = null
70
+ nako.addListener('beforeParse', (code) => { received = code })
71
+ await nako.runAsync('1と2を足して表示', 'main.nako3')
72
+ assert.strictEqual(received, '1と2を足して表示')
73
+ })
74
+
75
+ it('beforeRunとfinishには実行環境が渡される', async () => {
76
+ const nako = new NakoCompiler()
77
+ const received = []
78
+ nako.addListener('beforeRun', (g) => { received.push(g) })
79
+ nako.addListener('finish', (g) => { received.push(g) })
80
+ const g = await nako.runAsync('「テスト」を表示', 'main.nako3')
81
+ assert.strictEqual(received.length, 2)
82
+ assert.strictEqual(received[0], g)
83
+ assert.strictEqual(received[1], g)
84
+ })
85
+ })
@@ -0,0 +1,178 @@
1
+ /* eslint-disable no-undef */
2
+ /**
3
+ * nako_gen.mts のパフォーマンスモニタ / 関数呼び出しコード生成のテスト (#2333)
4
+ *
5
+ * `「〜」にパフォーマンスモニタ適用` で生成されるコードと、
6
+ * 実行時に作られる `__self.__performance_monitor` の内容を検証する。
7
+ * nako_gen.mts のリファクタリングで生成コードが壊れていないことを保証するのが目的。
8
+ */
9
+ import { describe, it } from 'node:test'
10
+ import assert from 'assert'
11
+ import { NakoCompiler } from '../src/nako3.mjs'
12
+
13
+ describe('nako_gen_perf_test', async () => {
14
+ /**
15
+ * なでしこコードを実行して、ログと計測結果を返す
16
+ * @param {string} code
17
+ */
18
+ const run = async (code) => {
19
+ const nako = new NakoCompiler()
20
+ const g = await nako.runAsync(code, 'main.nako3')
21
+ // 非同期命令は runAsync の完了後も動いているので待つ (func_call.mjs と同様)
22
+ if (code.indexOf('秒待') >= 0) { await forceWait(200) }
23
+ return { log: g.log, pm: g.__performance_monitor }
24
+ }
25
+
26
+ // 強制的にミリ秒待機
27
+ function forceWait (/** @type {number} */ ms) {
28
+ return /** @type {Promise<void>} */(new Promise((resolve) => {
29
+ setTimeout(() => { resolve() }, ms)
30
+ }))
31
+ }
32
+
33
+ describe('計測なしのとき', async () => {
34
+ it('__performance_monitor は作られない', async () => {
35
+ const { log, pm } = await run('「あ」と表示')
36
+ assert.strictEqual(log, 'あ')
37
+ assert.strictEqual(pm, undefined)
38
+ })
39
+ })
40
+
41
+ describe('システム関数本体の計測', async () => {
42
+ it('キーは『命令名_body』、typeは『sysbody』', async () => {
43
+ const { log, pm } = await run(
44
+ '「システム関数本体」にパフォーマンスモニタ適用ここから\n' +
45
+ ' 「あ」と表示\n' +
46
+ ' 1に2を足して表示\n' +
47
+ 'ここまで\n')
48
+ assert.strictEqual(log, 'あ\n3')
49
+ assert.strictEqual(pm['表示_body'].type, 'sysbody')
50
+ assert.strictEqual(pm['表示_body'].called, 2)
51
+ assert.strictEqual(pm['足_body'].type, 'sysbody')
52
+ assert.strictEqual(pm['足_body'].called, 1)
53
+ })
54
+ })
55
+
56
+ describe('システム関数の計測', async () => {
57
+ it('キーは『命令名_sys』、typeは『system』', async () => {
58
+ const { log, pm } = await run(
59
+ '「システム関数」にパフォーマンスモニタ適用ここから\n' +
60
+ ' 「あ」と表示\n' +
61
+ ' S=1に2を足す\n' +
62
+ ' Sを表示\n' +
63
+ 'ここまで\n')
64
+ assert.strictEqual(log, 'あ\n3')
65
+ assert.strictEqual(pm['表示_sys'].type, 'system')
66
+ assert.strictEqual(pm['表示_sys'].called, 2)
67
+ assert.strictEqual(pm['足_sys'].type, 'system')
68
+ assert.strictEqual(pm['足_sys'].called, 1)
69
+ })
70
+
71
+ it('計測結果には called/totel_usec/min_usec/max_usec が入る', async () => {
72
+ const { pm } = await run(
73
+ '「システム関数」にパフォーマンスモニタ適用ここから\n' +
74
+ ' 「あ」と表示\n' +
75
+ 'ここまで\n')
76
+ const r = pm['表示_sys']
77
+ assert.deepStrictEqual(Object.keys(r).sort(), ['called', 'max_usec', 'min_usec', 'totel_usec', 'type'].sort())
78
+ assert.ok(r.min_usec <= r.max_usec)
79
+ assert.ok(r.totel_usec >= r.max_usec)
80
+ })
81
+
82
+ it('ブロックの外は計測されない', async () => {
83
+ const { log, pm } = await run(
84
+ '「システム関数」にパフォーマンスモニタ適用ここから\n' +
85
+ ' 「あ」と表示\n' +
86
+ 'ここまで\n' +
87
+ '1に2を足して表示\n')
88
+ assert.strictEqual(log, 'あ\n3')
89
+ assert.strictEqual(pm['表示_sys'].called, 1)
90
+ assert.strictEqual(pm['足_sys'], undefined)
91
+ })
92
+ })
93
+
94
+ describe('ユーザ関数の計測', async () => {
95
+ it('キーは関数名、typeは『user』 (#2333)', async () => {
96
+ const { log, pm } = await run(
97
+ '「ユーザ関数」にパフォーマンスモニタ適用ここから\n' +
98
+ ' ●(AとBを)テスト足すとは\n' +
99
+ '  AにBを足して戻す\n' +
100
+ ' ここまで\n' +
101
+ ' 1と2をテスト足して表示\n' +
102
+ ' 3と4をテスト足して表示\n' +
103
+ 'ここまで\n')
104
+ assert.strictEqual(log, '3\n7')
105
+ assert.strictEqual(pm.main__テスト足.type, 'user')
106
+ assert.strictEqual(pm.main__テスト足.called, 2)
107
+ })
108
+ })
109
+
110
+ describe('非同期命令の計測 (#2333)', async () => {
111
+ it('システム関数本体: 構文エラーにならず、実行も完了する', async () => {
112
+ const { log, pm } = await run(
113
+ '「システム関数本体」にパフォーマンスモニタ適用ここから\n' +
114
+ ' 0.01秒待つ\n' +
115
+ ' 「おわり」と表示\n' +
116
+ 'ここまで\n')
117
+ assert.strictEqual(log, 'おわり')
118
+ assert.strictEqual(pm.秒待_body.type, 'sysbody')
119
+ assert.strictEqual(pm.秒待_body.called, 1)
120
+ })
121
+
122
+ it('システム関数: 構文エラーにならず、実行も完了する', async () => {
123
+ const { log, pm } = await run(
124
+ '「システム関数」にパフォーマンスモニタ適用ここから\n' +
125
+ ' 0.01秒待つ\n' +
126
+ ' 「おわり」と表示\n' +
127
+ 'ここまで\n')
128
+ assert.strictEqual(log, 'おわり')
129
+ assert.strictEqual(pm.秒待_sys.type, 'system')
130
+ assert.strictEqual(pm.秒待_sys.called, 1)
131
+ })
132
+ })
133
+
134
+ describe('全てのオプションを同時に適用 (#2333)', async () => {
135
+ it('ユーザ関数・システム関数本体・システム関数を同時に計測できる', async () => {
136
+ const { log, pm } = await run(
137
+ '「全て」にパフォーマンスモニタ適用ここから\n' +
138
+ ' ●(Aを)テスト表示とは\n' +
139
+ '  Aを表示\n' +
140
+ ' ここまで\n' +
141
+ ' 「あ」をテスト表示\n' +
142
+ ' 1に2を足して表示\n' +
143
+ 'ここまで\n')
144
+ assert.strictEqual(log, 'あ\n3')
145
+ assert.strictEqual(pm.main__テスト表示.type, 'user')
146
+ assert.strictEqual(pm.足_body.type, 'sysbody')
147
+ assert.strictEqual(pm.足_sys.type, 'system')
148
+ })
149
+ })
150
+
151
+ describe('関数呼び出しコード生成の回帰チェック', async () => {
152
+ it('戻り値のある命令と『それ』', async () => {
153
+ const { log } = await run('1に2を足す\nそれを表示')
154
+ assert.strictEqual(log, '3')
155
+ })
156
+
157
+ it('ユーザ関数の呼び出しと戻り値', async () => {
158
+ const { log } = await run(
159
+ '●(AとBを)テスト足すとは\n' +
160
+ ' AにBを足して戻す\n' +
161
+ 'ここまで\n' +
162
+ '1と2をテスト足して表示\n')
163
+ assert.strictEqual(log, '3')
164
+ })
165
+
166
+ it('無名関数の呼び出し', async () => {
167
+ const { log } = await run(
168
+ 'F=関数(a,b)それはa+b;ここまで\n' +
169
+ 'F(3,4)を表示\n')
170
+ assert.strictEqual(log, '7')
171
+ })
172
+
173
+ it('非同期命令の呼び出し', async () => {
174
+ const { log } = await run('0.01秒待つ\n「おわり」と表示')
175
+ assert.strictEqual(log, 'おわり')
176
+ })
177
+ })
178
+ })
@@ -0,0 +1,112 @@
1
+ /* eslint-disable no-undef */
2
+ /**
3
+ * nako_parser_async.mts の単体テスト (#2364)
4
+ *
5
+ * 非同期処理(asyncFn)の判定は、これまで NakoParser の内部メソッドだったため
6
+ * 単体で検証できなかった。分離したことで AST を直接組み立てて確認できる。
7
+ */
8
+ import { describe, it } from 'node:test'
9
+ import assert from 'assert'
10
+ import { checkAsyncFn } from '../src/nako_parser_async.mjs'
11
+ import { NakoCompiler } from '../src/nako3.mjs'
12
+
13
+ /** テスト用に func ノードを作る */
14
+ function funcNode (name, blocks = [], asyncFn = false) {
15
+ return { type: 'func', name, blocks, asyncFn, josi: '' }
16
+ }
17
+
18
+ /** テスト用に def_func ノードを作る */
19
+ function defFuncNode (name, blocks = []) {
20
+ return { type: 'def_func', name, blocks, meta: {}, asyncFn: false, josi: '' }
21
+ }
22
+
23
+ /** テスト用に block ノードを作る */
24
+ function blockNode (blocks) {
25
+ return { type: 'block', blocks, josi: '' }
26
+ }
27
+
28
+ describe('nako_parser_async_test', () => {
29
+ describe('checkAsyncFn', () => {
30
+ it('非同期処理が無ければ書き換えない', () => {
31
+ const ast = blockNode([funcNode('表示', [{ type: 'number', value: 1, josi: 'を' }])])
32
+ assert.strictEqual(checkAsyncFn(ast, new Map()), false)
33
+ assert.strictEqual(ast.blocks[0].asyncFn, false)
34
+ })
35
+
36
+ it('funclist が非同期の関数を呼ぶと asyncFn が付く', () => {
37
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
38
+ const ast = blockNode([funcNode('待つ')])
39
+ assert.strictEqual(checkAsyncFn(ast, funclist), true, '書き換えたので true')
40
+ assert.strictEqual(ast.blocks[0].asyncFn, true)
41
+ })
42
+
43
+ it('書き換えが済めば false を返す(不動点に到達する)', () => {
44
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
45
+ const ast = blockNode([funcNode('待つ')])
46
+ assert.strictEqual(checkAsyncFn(ast, funclist), true)
47
+ assert.strictEqual(checkAsyncFn(ast, funclist), false, '2回目は変化しない')
48
+ })
49
+
50
+ it('関数定義の中身が非同期なら関数定義自体が非同期になる', () => {
51
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
52
+ const def = defFuncNode('F', [funcNode('待つ')])
53
+ assert.strictEqual(checkAsyncFn(blockNode([def]), funclist), true)
54
+ assert.strictEqual(def.asyncFn, true)
55
+ assert.strictEqual(def.meta.asyncFn, true, 'meta にも反映される')
56
+ })
57
+
58
+ it('引数の中に非同期があれば呼び出し自体も非同期になる', () => {
59
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
60
+ const inner = funcNode('待つ')
61
+ const outer = funcNode('表示', [inner])
62
+ assert.strictEqual(checkAsyncFn(blockNode([outer]), funclist), true)
63
+ assert.strictEqual(outer.asyncFn, true)
64
+ })
65
+
66
+ it('連文は常に非同期として扱う', () => {
67
+ const def = defFuncNode('F', [{ type: 'renbun', blocks: [], josi: '' }])
68
+ assert.strictEqual(checkAsyncFn(blockNode([def]), new Map()), true)
69
+ assert.strictEqual(def.asyncFn, true)
70
+ })
71
+
72
+ it('[現状の挙動] block の走査は最初の非同期ノードで打ち切られる', () => {
73
+ // ブロックの走査は子が非同期だと分かった時点で return するため、
74
+ // それより後ろの兄弟はそのパスでは訪問されない。
75
+ // したがって G は、この走査だけでは非同期と判定されない。
76
+ //
77
+ // 実際のパーサでは yDefFuncCommon が解析中に usedAsyncFn を追跡して
78
+ // 関数定義に asyncFn を付けるので、この走査に頼らなくても G は非同期になる
79
+ // (下の「NakoParser 経由での結合確認」を参照)。
80
+ // 分離前からの挙動なので、そのまま固定しておく。
81
+ const funclist = new Map([
82
+ ['待つ', { type: 'func', asyncFn: true }],
83
+ ['F', { type: 'func', asyncFn: true }]
84
+ ])
85
+ const defF = defFuncNode('F', [funcNode('待つ')])
86
+ const defG = defFuncNode('G', [funcNode('F')])
87
+ const ast = blockNode([defF, defG])
88
+
89
+ let count = 0
90
+ while (checkAsyncFn(ast, funclist)) {
91
+ count++
92
+ assert.ok(count < 10, '不動点に収束しない')
93
+ }
94
+ assert.strictEqual(defF.asyncFn, true)
95
+ assert.strictEqual(defG.asyncFn, false, 'F の後ろにある G は訪問されない')
96
+ })
97
+ })
98
+
99
+ describe('NakoParser 経由での結合確認', () => {
100
+ it('非同期関数を呼ぶユーザー関数に asyncFn が伝播する', () => {
101
+ const ast = new NakoCompiler().parse('●Fとは\n1秒待つ\nここまで\nF\n', 'main.nako3')
102
+ const def = ast.blocks.find((n) => n.type === 'def_func')
103
+ assert.strictEqual(def.asyncFn, true)
104
+ })
105
+
106
+ it('非同期処理を含まない関数は asyncFn にならない', () => {
107
+ const ast = new NakoCompiler().parse('●Fとは\n1を表示\nここまで\nF\n', 'main.nako3')
108
+ const def = ast.blocks.find((n) => n.type === 'def_func')
109
+ assert.strictEqual(def.asyncFn, false)
110
+ })
111
+ })
112
+ })