nadesiko3 3.7.21 → 3.7.23

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 (212) hide show
  1. package/README.md +20 -1
  2. package/batch/command.txt +370 -334
  3. package/batch/generate_command_list.mjs +102 -0
  4. package/batch/jsplugin2text.nako3 +1 -1
  5. package/batch/pickup_command.nako3 +12 -0
  6. package/batch/search_command.mjs +34 -0
  7. package/batch/search_command.nako3 +430 -0
  8. package/core/command/snako.mts +5 -1
  9. package/core/deno/snako.ts +5 -1
  10. package/core/package-lock.json +382 -21
  11. package/core/package.json +7 -4
  12. package/core/sample/algorithms/README.md +43 -0
  13. package/core/sample/algorithms/graph/dijkstra.nako3 +47 -0
  14. package/core/sample/algorithms/graph/kruskal.nako3 +69 -0
  15. package/core/sample/algorithms/math/euclidean_gcd.nako3 +15 -0
  16. package/core/sample/algorithms/math/sieve_of_eratosthenes.nako3 +26 -0
  17. package/core/sample/algorithms/search/binary_search.nako3 +17 -0
  18. package/core/sample/algorithms/search/breadth_first_search.nako3 +33 -0
  19. package/core/sample/algorithms/search/depth_first_search.nako3 +25 -0
  20. package/core/sample/algorithms/sort/heap_sort.nako3 +37 -0
  21. package/core/sample/algorithms/sort/insertion_sort.nako3 +18 -0
  22. package/core/sample/algorithms/sort/merge_sort.nako3 +40 -0
  23. package/core/sample/algorithms/sort/quick_sort.nako3 +36 -0
  24. package/core/sample/algorithms/string/knuth_morris_pratt.nako3 +50 -0
  25. package/core/sample/algorithms/string/run_length_encoding.nako3 +25 -0
  26. package/core/src/nako3.mts +178 -614
  27. package/core/src/nako_basic_plugins.mts +39 -0
  28. package/core/src/nako_core_version.mts +2 -2
  29. package/core/src/nako_csv.mts +0 -1
  30. package/core/src/nako_event.mts +49 -0
  31. package/core/src/nako_gen.mts +320 -209
  32. package/core/src/nako_global.mts +18 -0
  33. package/core/src/nako_indent_inline.mts +2 -2
  34. package/core/src/nako_lex_rules.mts +1 -1
  35. package/core/src/nako_parser3.mts +111 -166
  36. package/core/src/nako_parser_async.mts +165 -0
  37. package/core/src/nako_parser_base.mts +4 -55
  38. package/core/src/nako_parser_message.mts +105 -0
  39. package/core/src/nako_parser_operator.mts +93 -0
  40. package/core/src/nako_plugin_manager.mts +260 -0
  41. package/core/src/nako_require.mts +292 -0
  42. package/core/src/nako_runner.mts +208 -0
  43. package/core/src/nako_tokenizer.mts +221 -0
  44. package/core/src/plugin_csv.mts +1 -1
  45. package/core/src/plugin_system.mts +34 -3259
  46. package/core/src/plugin_system_array.mts +699 -0
  47. package/core/src/plugin_system_datetime.mts +368 -0
  48. package/core/src/plugin_system_debug.mts +403 -0
  49. package/core/src/plugin_system_dict.mts +85 -0
  50. package/core/src/plugin_system_json.mts +73 -0
  51. package/core/src/plugin_system_math.mts +383 -0
  52. package/core/src/plugin_system_regexp.mts +120 -0
  53. package/core/src/plugin_system_stdio.mts +86 -0
  54. package/core/src/plugin_system_string.mts +666 -0
  55. package/core/src/plugin_system_timer.mts +152 -0
  56. package/core/src/plugin_system_types.mts +151 -0
  57. package/core/src/plugin_system_url.mts +193 -0
  58. package/core/src/plugin_toml.mts +3 -3
  59. package/core/test/algorithm_samples_test.mjs +58 -0
  60. package/core/test/algorithms/graph/dijkstra_test.nako3 +21 -0
  61. package/core/test/algorithms/graph/kruskal_test.nako3 +21 -0
  62. package/core/test/algorithms/helper.mjs +13 -0
  63. package/core/test/algorithms/math/euclidean_gcd_test.nako3 +13 -0
  64. package/core/test/algorithms/math/sieve_of_eratosthenes_test.nako3 +10 -0
  65. package/core/test/algorithms/search/binary_search_test.nako3 +15 -0
  66. package/core/test/algorithms/search/breadth_first_search_test.nako3 +13 -0
  67. package/core/test/algorithms/search/depth_first_search_test.nako3 +13 -0
  68. package/core/test/algorithms/sort/heap_sort_test.nako3 +13 -0
  69. package/core/test/algorithms/sort/insertion_sort_test.nako3 +13 -0
  70. package/core/test/algorithms/sort/merge_sort_test.nako3 +18 -0
  71. package/core/test/algorithms/sort/quick_sort_test.nako3 +34 -0
  72. package/core/test/algorithms/string/knuth_morris_pratt_test.nako3 +16 -0
  73. package/core/test/algorithms/string/run_length_encoding_test.nako3 +13 -0
  74. package/core/test/array_test.mjs +3 -0
  75. package/core/test/fixtures/README.md +39 -0
  76. package/core/test/fixtures/make_parser_ast_golden.mjs +31 -0
  77. package/core/test/fixtures/parser_ast_golden.json +8027 -0
  78. package/core/test/fixtures/parser_corpus.mjs +120 -0
  79. package/core/test/func_call.mjs +18 -0
  80. package/core/test/func_test.mjs +28 -0
  81. package/core/test/indent_test.mjs +6 -0
  82. package/core/test/nako_basic_plugins_test.mjs +44 -0
  83. package/core/test/nako_event_test.mjs +127 -0
  84. package/core/test/nako_gen_perf_test.mjs +178 -0
  85. package/core/test/nako_parser_async_test.mjs +385 -0
  86. package/core/test/nako_parser_test.mjs +160 -0
  87. package/core/test/nako_plugin_manager_test.mjs +185 -0
  88. package/core/test/nako_require_test.mjs +153 -0
  89. package/core/test/nako_runner_test.mjs +174 -0
  90. package/core/test/nako_tokenizer_test.mjs +115 -0
  91. package/core/test/plugin_system_debug_test.mjs +138 -0
  92. package/core/test/plugin_system_split_test.mjs +179 -0
  93. package/core/test/plugin_system_test.mjs +6 -0
  94. package/core/tsconfig.json +0 -1
  95. package/demo/browsers.html +1 -1
  96. package/doc/SETUP.md +1 -1
  97. package/doc/ai-code-generation.md +136 -0
  98. package/doc/browsers.md +1 -1
  99. package/doc/command_list.json +16842 -0
  100. package/doc/search_command.md +222 -0
  101. package/doc/syntax-nako3.md +344 -0
  102. package/package.json +29 -26
  103. package/release/_hash.txt +40 -40
  104. package/release/_script-tags.txt +16 -16
  105. package/release/command.json +1 -1
  106. package/release/command.json.js +1 -1
  107. package/release/command_cnako3.json +1 -1
  108. package/release/command_list.json +1 -1
  109. package/release/edit_main.js +6 -6
  110. package/release/edit_main.js.map +3 -3
  111. package/release/editor.js +6 -6
  112. package/release/plugin_caniuse.js +1 -1
  113. package/release/plugin_caniuse.js.map +2 -2
  114. package/release/plugin_keigo.js.map +3 -3
  115. package/release/plugin_markup.js +46 -46
  116. package/release/plugin_markup.js.map +3 -3
  117. package/release/plugin_weykturtle3d.js +1 -1
  118. package/release/plugin_weykturtle3d.js.map +3 -3
  119. package/release/version.js +2 -2
  120. package/release/version_main.js +2 -2
  121. package/release/version_main.js.map +1 -1
  122. package/release/wnako3.js +219 -230
  123. package/release/wnako3.js.map +4 -4
  124. package/release/wnako3webworker.js +193 -204
  125. package/release/wnako3webworker.js.map +4 -4
  126. package/src/browsers.mjs +1 -1
  127. package/src/browsers.txt +0 -1
  128. package/src/cnako3mod.mjs +7 -2
  129. package/src/cnako3mod.mts +7 -2
  130. package/src/nako_version.mjs +2 -2
  131. package/src/nako_version.mts +2 -2
  132. package/src/plugin_browser_ajax.mjs +4 -4
  133. package/src/plugin_browser_ajax.mts +4 -4
  134. package/src/plugin_browser_audio.mjs +10 -10
  135. package/src/plugin_browser_audio.mts +10 -10
  136. package/src/plugin_browser_camera.mjs +4 -4
  137. package/src/plugin_browser_camera.mts +4 -4
  138. package/src/plugin_browser_canvas.mjs +2 -2
  139. package/src/plugin_browser_canvas.mts +2 -2
  140. package/src/plugin_browser_crypto.mjs +3 -3
  141. package/src/plugin_browser_crypto.mts +3 -3
  142. package/src/plugin_browser_dom_event.mjs +1 -1
  143. package/src/plugin_browser_dom_event.mts +1 -1
  144. package/src/plugin_browser_geolocation.mjs +1 -1
  145. package/src/plugin_browser_geolocation.mts +1 -1
  146. package/src/plugin_browser_hotkey.mjs +1 -1
  147. package/src/plugin_browser_hotkey.mts +1 -1
  148. package/src/plugin_browser_html.mjs +1 -1
  149. package/src/plugin_browser_html.mts +1 -1
  150. package/src/plugin_browser_location.mjs +1 -1
  151. package/src/plugin_browser_location.mts +1 -1
  152. package/src/plugin_browser_speech.mjs +1 -1
  153. package/src/plugin_browser_speech.mts +1 -1
  154. package/src/plugin_browser_storage.mjs +2 -2
  155. package/src/plugin_browser_storage.mts +2 -2
  156. package/src/plugin_httpserver.mjs +3 -3
  157. package/src/plugin_httpserver.mts +3 -3
  158. package/src/plugin_keigo.mjs +2 -2
  159. package/src/plugin_keigo.mts +2 -2
  160. package/src/plugin_node.mjs +48 -48
  161. package/src/plugin_node.mts +53 -53
  162. package/src/plugin_weykturtle3d.mjs +56 -56
  163. package/src/plugin_weykturtle3d.mts +56 -56
  164. package/src/wnako3.mjs +1 -1
  165. package/src/wnako3.mts +1 -1
  166. package/src/wnako3_editor.mjs +5 -5
  167. package/src/wnako3_editor.mts +5 -5
  168. package/src/wnako3mod.mjs +2 -2
  169. package/src/wnako3mod.mts +2 -2
  170. package/test/nako3edit/index_nako3_test.mjs +282 -0
  171. package/test/nako3server/index_nako3_test.mjs +239 -0
  172. package/test/node/ai_code_generation_guide_test.mjs +29 -0
  173. package/test/node/node_version_support_test.mjs +45 -0
  174. package/test/node/search_command_test.mjs +174 -0
  175. package/tools/nako3edit/AGENTS.md +23 -0
  176. package/tools/nako3edit/index.nako3 +404 -0
  177. package/tools/nako3server/AGENTS.md +23 -0
  178. package/tools/nako3server/index.nako3 +156 -23
  179. package/core/src/nako3.mjs +0 -1021
  180. package/core/src/nako_ast.mjs +0 -4
  181. package/core/src/nako_colors.mjs +0 -77
  182. package/core/src/nako_core_version.mjs +0 -8
  183. package/core/src/nako_csv.mjs +0 -193
  184. package/core/src/nako_errors.mjs +0 -166
  185. package/core/src/nako_from_dncl.mjs +0 -285
  186. package/core/src/nako_from_dncl2.mjs +0 -347
  187. package/core/src/nako_gen.mjs +0 -2500
  188. package/core/src/nako_global.mjs +0 -138
  189. package/core/src/nako_indent.mjs +0 -442
  190. package/core/src/nako_indent_chars.mjs +0 -29
  191. package/core/src/nako_indent_inline.mjs +0 -361
  192. package/core/src/nako_josi_list.mjs +0 -47
  193. package/core/src/nako_lex_rules.mjs +0 -319
  194. package/core/src/nako_lexer.mjs +0 -794
  195. package/core/src/nako_logger.mjs +0 -221
  196. package/core/src/nako_parser3.mjs +0 -3250
  197. package/core/src/nako_parser_base.mjs +0 -403
  198. package/core/src/nako_parser_const.mjs +0 -37
  199. package/core/src/nako_prepare.mjs +0 -329
  200. package/core/src/nako_reserved_words.mjs +0 -42
  201. package/core/src/nako_source_mapping.mjs +0 -207
  202. package/core/src/nako_test.mjs +0 -37
  203. package/core/src/nako_token.mjs +0 -1
  204. package/core/src/nako_tools.mjs +0 -53
  205. package/core/src/nako_types.mjs +0 -14
  206. package/core/src/plugin_api.mjs +0 -4
  207. package/core/src/plugin_csv.mjs +0 -97
  208. package/core/src/plugin_math.mjs +0 -352
  209. package/core/src/plugin_promise.mjs +0 -102
  210. package/core/src/plugin_system.mjs +0 -3810
  211. package/core/src/plugin_test.mjs +0 -52
  212. package/core/src/plugin_toml.mjs +0 -39
@@ -0,0 +1,385 @@
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
+ /** テスト用に func_obj(無名関数) ノードを作る */
29
+ function funcObjNode (blocks = [], asyncFn = false) {
30
+ return { type: 'func_obj', name: '', blocks, meta: {}, asyncFn, josi: '' }
31
+ }
32
+
33
+ /** テスト用に let(代入) ノードを作る */
34
+ function letNode (name, value) {
35
+ return { type: 'let', name, blocks: [value], josi: '' }
36
+ }
37
+
38
+ /**
39
+ * なでしこのプログラムを実行してログを得る
40
+ *
41
+ * runAsync は生成コードの実行完了を待ってから戻る。
42
+ */
43
+ async function runAndGetLog (code) {
44
+ const g = await new NakoCompiler().runAsync(code, 'main.nako3')
45
+ return g.log
46
+ }
47
+
48
+ describe('nako_parser_async_test', () => {
49
+ describe('checkAsyncFn', () => {
50
+ it('非同期処理が無ければ書き換えない', () => {
51
+ const ast = blockNode([funcNode('表示', [{ type: 'number', value: 1, josi: 'を' }])])
52
+ assert.strictEqual(checkAsyncFn(ast, new Map()), false)
53
+ assert.strictEqual(ast.blocks[0].asyncFn, false)
54
+ })
55
+
56
+ it('funclist が非同期の関数を呼ぶと asyncFn が付く', () => {
57
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
58
+ const ast = blockNode([funcNode('待つ')])
59
+ assert.strictEqual(checkAsyncFn(ast, funclist), true, '書き換えたので true')
60
+ assert.strictEqual(ast.blocks[0].asyncFn, true)
61
+ })
62
+
63
+ it('書き換えが済めば false を返す(不動点に到達する)', () => {
64
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
65
+ const ast = blockNode([funcNode('待つ')])
66
+ assert.strictEqual(checkAsyncFn(ast, funclist), true)
67
+ assert.strictEqual(checkAsyncFn(ast, funclist), false, '2回目は変化しない')
68
+ })
69
+
70
+ it('関数定義の中身が非同期なら関数定義自体が非同期になる', () => {
71
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
72
+ const def = defFuncNode('F', [funcNode('待つ')])
73
+ assert.strictEqual(checkAsyncFn(blockNode([def]), funclist), true)
74
+ assert.strictEqual(def.asyncFn, true)
75
+ assert.strictEqual(def.meta.asyncFn, true, 'meta にも反映される')
76
+ })
77
+
78
+ it('引数の中に非同期があれば呼び出し自体も非同期になる', () => {
79
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
80
+ const inner = funcNode('待つ')
81
+ const outer = funcNode('表示', [inner])
82
+ assert.strictEqual(checkAsyncFn(blockNode([outer]), funclist), true)
83
+ assert.strictEqual(outer.asyncFn, true)
84
+ })
85
+
86
+ it('引数の走査も最初の非同期ノードで打ち切らない', () => {
87
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
88
+ const arg1 = funcNode('待つ')
89
+ const arg2 = funcNode('待つ')
90
+ const outer = funcNode('足', [arg1, arg2])
91
+ const ast = blockNode([outer])
92
+
93
+ let count = 0
94
+ while (checkAsyncFn(ast, funclist)) {
95
+ count++
96
+ assert.ok(count < 10, '不動点に収束しない')
97
+ }
98
+ assert.strictEqual(outer.asyncFn, true)
99
+ assert.strictEqual(arg1.asyncFn, true)
100
+ assert.strictEqual(arg2.asyncFn, true, '2つ目の引数も訪問される')
101
+ })
102
+
103
+ it('連文は常に非同期として扱う', () => {
104
+ const def = defFuncNode('F', [{ type: 'renbun', blocks: [], josi: '' }])
105
+ assert.strictEqual(checkAsyncFn(blockNode([def]), new Map()), true)
106
+ assert.strictEqual(def.asyncFn, true)
107
+ })
108
+
109
+ it('block 内の複数の非同期関数をすべて走査する', () => {
110
+ const funclist = new Map([
111
+ ['待つ', { type: 'func', asyncFn: true }],
112
+ ['F', { type: 'func', asyncFn: false }],
113
+ ['G', { type: 'func', asyncFn: false }]
114
+ ])
115
+ const defF = defFuncNode('F', [funcNode('待つ')])
116
+ const defG = defFuncNode('G', [funcNode('F')])
117
+ const ast = blockNode([defF, defG])
118
+
119
+ let count = 0
120
+ while (checkAsyncFn(ast, funclist)) {
121
+ count++
122
+ assert.ok(count < 10, '不動点に収束しない')
123
+ }
124
+ assert.strictEqual(defF.asyncFn, true)
125
+ assert.strictEqual(defG.asyncFn, true, 'F の後ろにある G も訪問される')
126
+ assert.strictEqual(funclist.get('F').asyncFn, true)
127
+ assert.strictEqual(funclist.get('G').asyncFn, true)
128
+ })
129
+
130
+ it('関数定義の本体も最初の非同期ノードで打ち切らない', () => {
131
+ // 本体の1文目が非同期でも、2文目以降を走査しないと
132
+ // そこにある呼び出しに asyncFn が付かない
133
+ const funclist = new Map([
134
+ ['待つ', { type: 'func', asyncFn: true }],
135
+ ['G', { type: 'func', asyncFn: true }],
136
+ ['F', { type: 'func', asyncFn: false }]
137
+ ])
138
+ const callG = funcNode('G')
139
+ const def = defFuncNode('F', [funcNode('待つ'), callG])
140
+
141
+ let count = 0
142
+ while (checkAsyncFn(blockNode([def]), funclist)) {
143
+ count++
144
+ assert.ok(count < 10, '不動点に収束しない')
145
+ }
146
+ assert.strictEqual(def.asyncFn, true)
147
+ assert.strictEqual(callG.asyncFn, true, '1文目より後ろの呼び出しも訪問される')
148
+ })
149
+
150
+ it('非同期な無名関数を保持する変数の呼び出しも非同期になる', () => {
151
+ // F=●()...ここまで のように変数へ代入された無名関数は funclist に載らないため、
152
+ // 代入を追跡しないと呼び出し側に asyncFn が付かない
153
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
154
+ const anon = funcObjNode([funcNode('待つ')])
155
+ const callF = funcNode('F')
156
+ const ast = blockNode([letNode('F', anon), callF])
157
+
158
+ let count = 0
159
+ while (checkAsyncFn(ast, funclist)) {
160
+ count++
161
+ assert.ok(count < 10, '不動点に収束しない')
162
+ }
163
+ assert.strictEqual(anon.asyncFn, true)
164
+ assert.strictEqual(callF.asyncFn, true, '変数経由の呼び出しにも asyncFn が付く')
165
+ })
166
+
167
+ it('同期な無名関数を保持する変数は非同期にならない', () => {
168
+ const anon = funcObjNode([funcNode('表示')])
169
+ const callF = funcNode('F')
170
+ const ast = blockNode([letNode('F', anon), callF])
171
+ assert.strictEqual(checkAsyncFn(ast, new Map()), false)
172
+ assert.strictEqual(callF.asyncFn, false)
173
+ })
174
+
175
+ it('無名関数の変数はその関数スコープの外へ漏れない', () => {
176
+ // 関数 F の中のローカル変数 X と、外側の変数 X は別物として扱う
177
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
178
+ const innerAnon = funcObjNode([funcNode('待つ')])
179
+ const defF = defFuncNode('F', [letNode('X', innerAnon)])
180
+ const outerCallX = funcNode('X')
181
+ const ast = blockNode([defF, outerCallX])
182
+
183
+ let count = 0
184
+ while (checkAsyncFn(ast, funclist)) {
185
+ count++
186
+ assert.ok(count < 10, '不動点に収束しない')
187
+ }
188
+ assert.strictEqual(innerAnon.asyncFn, true)
189
+ assert.strictEqual(outerCallX.asyncFn, false, '関数の外の X は非同期にならない')
190
+ })
191
+
192
+ it('内側で同名の同期な無名関数を代入すると外側の非同期をシャドーイングする', () => {
193
+ // 外側の X は非同期だが、内側の関数で同名の同期な無名関数を代入しているので、
194
+ // 内側の X() は非同期にならない
195
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
196
+ const outerAnon = funcObjNode([funcNode('待つ')])
197
+ const innerAnon = funcObjNode([funcNode('表示')])
198
+ const innerCallX = funcNode('X')
199
+ const defG = defFuncNode('G', [letNode('X', innerAnon), innerCallX])
200
+ const ast = blockNode([letNode('X', outerAnon), defG])
201
+
202
+ let count = 0
203
+ while (checkAsyncFn(ast, funclist)) {
204
+ count++
205
+ assert.ok(count < 10, '不動点に収束しない')
206
+ }
207
+ assert.strictEqual(outerAnon.asyncFn, true)
208
+ assert.strictEqual(innerAnon.asyncFn, false)
209
+ assert.strictEqual(innerCallX.asyncFn, false, '内側の同期な X が優先される')
210
+ assert.strictEqual(defG.asyncFn, false, 'G も非同期にならない')
211
+ })
212
+
213
+ it('内側で同名の変数を使っていなければ外側の非同期が見える', () => {
214
+ // 上のシャドーイングと対になる確認。内側に代入が無ければ外側の X を参照する
215
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
216
+ const outerAnon = funcObjNode([funcNode('待つ')])
217
+ const innerCallX = funcNode('X')
218
+ const defG = defFuncNode('G', [innerCallX])
219
+ const ast = blockNode([letNode('X', outerAnon), defG])
220
+
221
+ let count = 0
222
+ while (checkAsyncFn(ast, funclist)) {
223
+ count++
224
+ assert.ok(count < 10, '不動点に収束しない')
225
+ }
226
+ assert.strictEqual(innerCallX.asyncFn, true, '外側の非同期な X が見える')
227
+ })
228
+
229
+ it('同じスコープで同期な無名関数へ代入し直すと非同期でなくなる', () => {
230
+ const funclist = new Map([['待つ', { type: 'func', asyncFn: true }]])
231
+ const callBefore = funcNode('X')
232
+ const callAfter = funcNode('X')
233
+ const ast = blockNode([
234
+ letNode('X', funcObjNode([funcNode('待つ')])),
235
+ callBefore,
236
+ letNode('X', funcObjNode([funcNode('表示')])),
237
+ callAfter
238
+ ])
239
+
240
+ let count = 0
241
+ while (checkAsyncFn(ast, funclist)) {
242
+ count++
243
+ assert.ok(count < 10, '不動点に収束しない')
244
+ }
245
+ assert.strictEqual(callBefore.asyncFn, true, '代入し直す前は非同期')
246
+ assert.strictEqual(callAfter.asyncFn, false, '代入し直した後は同期')
247
+ })
248
+ })
249
+
250
+ describe('NakoParser 経由での結合確認', () => {
251
+ it('非同期関数を呼ぶユーザー関数に asyncFn が伝播する', () => {
252
+ const ast = new NakoCompiler().parse('●Fとは\n1秒待つ\nここまで\nF\n', 'main.nako3')
253
+ const def = ast.blocks.find((n) => n.type === 'def_func')
254
+ assert.strictEqual(def.asyncFn, true)
255
+ })
256
+
257
+ it('非同期処理を含まない関数は asyncFn にならない', () => {
258
+ const ast = new NakoCompiler().parse('●Fとは\n1を表示\nここまで\nF\n', 'main.nako3')
259
+ const def = ast.blocks.find((n) => n.type === 'def_func')
260
+ assert.strictEqual(def.asyncFn, false)
261
+ })
262
+
263
+ it('連文を代入する関数が複数続いてもすべてawaitされる (#2170)', async () => {
264
+ const code = `Aを表示。
265
+ Bを表示。
266
+
267
+ ●A
268
+ a=30に5を足して2を掛ける。
269
+ 「A関数内:」&aを表示。
270
+ aを戻す。
271
+ ここまで。
272
+
273
+ ●B
274
+ b=30に5を足して2を掛ける。
275
+ 「B関数内:」&bを表示。
276
+ bを戻す。
277
+ ここまで。`
278
+ assert.strictEqual(await runAndGetLog(code), 'A関数内:70\n70\nB関数内:70\n70')
279
+ })
280
+ })
281
+
282
+ describe('無名関数(関数オブジェクト)の非同期判定', () => {
283
+ it('連文を含む無名関数の呼び出しがawaitされる', async () => {
284
+ const code = `F=●()
285
+ b=1に2を足して3を掛ける。
286
+ bを戻す。
287
+ ここまで。
288
+ F()を表示。`
289
+ assert.strictEqual(await runAndGetLog(code), '9')
290
+ })
291
+
292
+ it('「変数」で宣言した無名関数の呼び出しもawaitされる', async () => {
293
+ const code = `変数 F=●()
294
+ b=1に2を足して3を掛ける。
295
+ bを戻す。
296
+ ここまで。
297
+ F()を表示。`
298
+ assert.strictEqual(await runAndGetLog(code), '9')
299
+ })
300
+
301
+ it('非同期のユーザー関数を呼ぶ無名関数もawaitされる', async () => {
302
+ const code = `●B
303
+ b=1に2を足して3を掛ける。
304
+ bを戻す。
305
+ ここまで。
306
+ F=●()
307
+ Bを戻す。
308
+ ここまで。
309
+ F()を表示。`
310
+ assert.strictEqual(await runAndGetLog(code), '9')
311
+ })
312
+
313
+ it('関数の中で定義した無名関数の呼び出しもawaitされる', async () => {
314
+ const code = `Aを実行。
315
+ ●A
316
+ F=●()
317
+ b=1に2を足して3を掛ける。
318
+ bを戻す。
319
+ ここまで。
320
+ F()を表示。
321
+ ここまで。`
322
+ assert.strictEqual(await runAndGetLog(code), '9')
323
+ })
324
+
325
+ it('非同期の文より後ろで定義した無名関数の呼び出しもawaitされる', async () => {
326
+ // 関数本体の走査が1文目で打ち切られていると F() に asyncFn が付かない
327
+ const code = `Aを実行。
328
+ ●A
329
+ a=30に5を足して2を掛ける。
330
+ aを表示。
331
+ F=●()
332
+ b=1に2を足して3を掛ける。
333
+ bを戻す。
334
+ ここまで。
335
+ F()を表示。
336
+ ここまで。`
337
+ assert.strictEqual(await runAndGetLog(code), '70\n9')
338
+ })
339
+
340
+ it('引数が2つとも非同期の無名関数でもすべてawaitされる', async () => {
341
+ // 引数の走査が1つ目で打ち切られていると G() に asyncFn が付かない
342
+ const code = `F=●()
343
+ b=1に2を足して3を掛ける。
344
+ bを戻す。
345
+ ここまで。
346
+ G=●()
347
+ c=2に2を足して2を掛ける。
348
+ cを戻す。
349
+ ここまで。
350
+ F()とG()を足して表示。`
351
+ assert.strictEqual(await runAndGetLog(code), '17')
352
+ })
353
+
354
+ it('内側で同名の同期な無名関数を代入しても実行結果が変わらない', async () => {
355
+ // 外側の F は非同期、内側の F は同期。取り違えると値が壊れる
356
+ const code = `●A
357
+ Fとは変数
358
+ F=●()
359
+ b=1に2を足して3を掛ける。
360
+ bを戻す。
361
+ ここまで。
362
+ G=●()
363
+ Fとは変数
364
+ F=●()
365
+ 5を戻す。
366
+ ここまで。
367
+ F()を戻す。
368
+ ここまで。
369
+ G()を表示。
370
+ F()を表示。
371
+ ここまで。
372
+ Aを実行。`
373
+ assert.strictEqual(await runAndGetLog(code), '5\n9')
374
+ })
375
+
376
+ it('非同期処理を含まない無名関数はawaitされない', async () => {
377
+ const code = `F=●()
378
+ b=1に2を足す。
379
+ bを戻す。
380
+ ここまで。
381
+ F()を表示。`
382
+ assert.strictEqual(await runAndGetLog(code), '3')
383
+ })
384
+ })
385
+ })
@@ -0,0 +1,160 @@
1
+ /* eslint-disable no-undef */
2
+ /**
3
+ * 構文解析器(NakoParser)の回帰テスト (#2364)
4
+ *
5
+ * `core/src/nako_parser3.mts` は 2,900 行を超える大きなファイルで、
6
+ * これまでパーサを直接検証するテストが無く、回帰は実行結果(runAsync)経由でしか
7
+ * 検出できなかった。パーサの内部構造に手を入れる際の安全網として、
8
+ * 構文解析の結果である AST そのものを検証する。
9
+ *
10
+ * 2 段構えで検証している。
11
+ *
12
+ * 1. 構造アサーション … パーサの仕様を人間が読める形で固定する
13
+ * 2. ゴールデン比較 … AST の全キー・全値・キーの並び順まで含めて完全一致を確認する
14
+ *
15
+ * コーパスとゴールデンファイルの更新方法は `fixtures/README.md` を参照。
16
+ */
17
+ import { describe, it } from 'node:test'
18
+ import assert from 'assert'
19
+ import fs from 'node:fs'
20
+ import { PARSER_CORPUS, parseToPlainAst } from './fixtures/parser_corpus.mjs'
21
+
22
+ /** ブロック直下のノード種別を並べる(構造アサーション用) */
23
+ function blockTypes (ast) {
24
+ return ast.blocks.filter((n) => n.type !== 'eol').map((n) => n.type)
25
+ }
26
+
27
+ /** 最初の(eol 以外の)文を取り出す */
28
+ function firstSentence (ast) {
29
+ return ast.blocks.find((n) => n.type !== 'eol')
30
+ }
31
+
32
+ describe('nako_parser_test', () => {
33
+ // -------------------------------------------------------------------------
34
+ // 1. 構造アサーション … パーサの仕様を人間が読める形で固定する
35
+ // -------------------------------------------------------------------------
36
+ describe('構造アサーション', () => {
37
+ it('文の並びが block になる', () => {
38
+ const ast = parseToPlainAst('A=1\nB=2\n')
39
+ assert.strictEqual(ast.type, 'block')
40
+ assert.deepStrictEqual(blockTypes(ast), ['let', 'let'])
41
+ })
42
+
43
+ it('代入は let になり変数名にモジュール名が付く', () => {
44
+ const let1 = firstSentence(parseToPlainAst('A=1\n'))
45
+ assert.strictEqual(let1.type, 'let')
46
+ assert.strictEqual(let1.name, 'main__A')
47
+ assert.strictEqual(let1.blocks[0].type, 'number')
48
+ assert.strictEqual(let1.blocks[0].value, 1)
49
+ })
50
+
51
+ it('関数呼び出しは func になり助詞で引数が結び付く', () => {
52
+ const func = firstSentence(parseToPlainAst('「あ」を表示\n'))
53
+ assert.strictEqual(func.type, 'func')
54
+ assert.strictEqual(func.name, '表示')
55
+ assert.strictEqual(func.blocks.length, 1)
56
+ assert.strictEqual(func.blocks[0].value, 'あ')
57
+ assert.strictEqual(func.blocks[0].josi, 'を')
58
+ })
59
+
60
+ it('演算子の優先順位が AST の入れ子に反映される', () => {
61
+ // 1+2*3 は 1+(2*3) になる
62
+ const func = firstSentence(parseToPlainAst('1+2*3を表示\n'))
63
+ const op = func.blocks[0]
64
+ assert.strictEqual(op.type, 'op')
65
+ assert.strictEqual(op.operator, '+')
66
+ assert.strictEqual(op.blocks[0].value, 1)
67
+ assert.strictEqual(op.blocks[1].operator, '*')
68
+ assert.strictEqual(op.blocks[1].blocks[0].value, 2)
69
+ assert.strictEqual(op.blocks[1].blocks[1].value, 3)
70
+ })
71
+
72
+ it('カッコが優先順位を上書きする', () => {
73
+ // (1+2)*3 は (1+2) が先
74
+ const func = firstSentence(parseToPlainAst('(1+2)*3を表示\n'))
75
+ const op = func.blocks[0]
76
+ assert.strictEqual(op.operator, '*')
77
+ assert.strictEqual(op.blocks[0].operator, '+')
78
+ assert.strictEqual(op.blocks[1].value, 3)
79
+ })
80
+
81
+ it('もし文は if になり blocks が [条件, 真, 偽] の順になる', () => {
82
+ const ifAst = firstSentence(parseToPlainAst('もしA=1ならば\nB=1\n違えば\nB=2\nここまで\n'))
83
+ assert.strictEqual(ifAst.type, 'if')
84
+ assert.strictEqual(ifAst.blocks.length, 3)
85
+ assert.strictEqual(ifAst.blocks[0].type, 'op')
86
+ assert.strictEqual(ifAst.blocks[0].operator, 'eq')
87
+ })
88
+
89
+ it('関数定義は def_func になり引数リストを持つ', () => {
90
+ // 『足す』は送り仮名が落ちて『足』になる
91
+ const def = firstSentence(parseToPlainAst('●(AとBを)足すとは\nA+Bで戻る\nここまで\n'))
92
+ assert.strictEqual(def.type, 'def_func')
93
+ assert.strictEqual(def.name, 'main__足')
94
+ assert.deepStrictEqual(def.meta.varnames, ['A', 'B'])
95
+ assert.deepStrictEqual(def.meta.josi, [['と'], ['を']])
96
+ })
97
+
98
+ it('配列リテラルは json_array になる', () => {
99
+ const let1 = firstSentence(parseToPlainAst('A=[1,2,3]\n'))
100
+ assert.strictEqual(let1.blocks[0].type, 'json_array')
101
+ assert.deepStrictEqual(let1.blocks[0].blocks.map((n) => n.value), [1, 2, 3])
102
+ })
103
+
104
+ it('辞書リテラルは json_obj になりキーと値が交互に並ぶ', () => {
105
+ const let1 = firstSentence(parseToPlainAst('A={"a":1,"b":2}\n'))
106
+ const obj = let1.blocks[0]
107
+ assert.strictEqual(obj.type, 'json_obj')
108
+ assert.deepStrictEqual(obj.blocks.map((n) => n.value), ['a', 1, 'b', 2])
109
+ })
110
+
111
+ it('配列要素への代入は let_array になる', () => {
112
+ const ast = parseToPlainAst('A=[0]\nA[0]=1\n')
113
+ assert.deepStrictEqual(blockTypes(ast), ['let', 'let_array'])
114
+ })
115
+
116
+ it('繰り返し文は for になる', () => {
117
+ const forAst = firstSentence(parseToPlainAst('Nを1から10まで繰り返す\nNを表示\nここまで\n'))
118
+ assert.strictEqual(forAst.type, 'for')
119
+ assert.strictEqual(forAst.blocks[0].value, 1)
120
+ assert.strictEqual(forAst.blocks[1].value, 10)
121
+ })
122
+
123
+ it('連文は block として接続される', () => {
124
+ const renbun = firstSentence(parseToPlainAst('1に2を足して3を掛けて表示\n'))
125
+ assert.strictEqual(renbun.type, 'block')
126
+ })
127
+
128
+ it('非同期関数を呼ぶ関数には asyncFn が伝播する', () => {
129
+ const ast = parseToPlainAst('●Fとは\n1秒待つ\nここまで\nF\n')
130
+ const def = ast.blocks.find((n) => n.type === 'def_func')
131
+ assert.ok(def, 'def_func が見つかりません')
132
+ assert.strictEqual(def.asyncFn, true, '『待つ』を呼ぶ関数Fは非同期になる')
133
+ })
134
+
135
+ it('エラー監視は try_except になる', () => {
136
+ const tryAst = firstSentence(parseToPlainAst('エラー監視\nA=1\nエラーならば\nB=2\nここまで\n'))
137
+ assert.strictEqual(tryAst.type, 'try_except')
138
+ assert.strictEqual(tryAst.blocks.length, 2)
139
+ })
140
+ })
141
+
142
+ // -------------------------------------------------------------------------
143
+ // 2. ゴールデン比較 … AST が 1 バイトも変わらないことを確認する
144
+ // -------------------------------------------------------------------------
145
+ describe('ゴールデン比較', () => {
146
+ const goldenPath = new URL('./fixtures/parser_ast_golden.json', import.meta.url)
147
+ const golden = JSON.parse(fs.readFileSync(goldenPath, 'utf8'))
148
+
149
+ it('ゴールデンとコーパスの項目が一致する', () => {
150
+ assert.deepStrictEqual(Object.keys(golden), Object.keys(PARSER_CORPUS),
151
+ 'コーパスを変更したらゴールデンを再生成すること (fixtures/README.md 参照)')
152
+ })
153
+
154
+ for (const [name, code] of Object.entries(PARSER_CORPUS)) {
155
+ it(name, () => {
156
+ assert.deepStrictEqual(parseToPlainAst(code), golden[name])
157
+ })
158
+ }
159
+ })
160
+ })