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,185 @@
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 { NakoPluginManager, PLUGIN_MIN_VERSION_INT } from '../src/nako_plugin_manager.mjs'
7
+ import { NakoLogger } from '../src/nako_logger.mjs'
8
+
9
+ /**
10
+ * プラグイン管理を NakoCompiler から分離したモジュールのテスト (#2360)
11
+ */
12
+ describe('nako_plugin_manager_test', () => {
13
+ /** テスト用にホストを差し替えた NakoPluginManager を作る */
14
+ const createManager = () => {
15
+ const funclist = new Map()
16
+ const sysVars = new Map()
17
+ const logger = new NakoLogger()
18
+ const manager = new NakoPluginManager({
19
+ getFuncList: () => funclist,
20
+ getSysVars: () => sysVars,
21
+ getLogger: () => logger
22
+ })
23
+ return { manager, funclist, sysVars, logger }
24
+ }
25
+
26
+ /** 現在のバージョン要求を満たすメタ情報を作る */
27
+ const newMeta = (pluginName, nakoVersion = '3.6.0') => {
28
+ return { type: 'const', value: { pluginName, nakoVersion } }
29
+ }
30
+
31
+ it('プラグイン名が重複した場合は登録されない', () => {
32
+ const nako = new NakoCompiler()
33
+ nako.addPlugin({
34
+ meta: newMeta('DuplicatedPlugin'),
35
+ 重複テスト値: { type: 'const', value: 1 }
36
+ })
37
+ nako.addPlugin({
38
+ meta: newMeta('DuplicatedPlugin'),
39
+ 重複テスト値: { type: 'const', value: 2 }
40
+ })
41
+ // 2回目の登録は無視されるので、最初に登録した値のままになる
42
+ assert.strictEqual(nako.getFunc('重複テスト値').value, 1)
43
+ })
44
+
45
+ it('metaが無い場合はプラグイン名をキー名から自動生成する', () => {
46
+ const { manager, sysVars } = createManager()
47
+ manager.addPlugin({ hoge: { type: 'const', value: 1 } })
48
+ const pluginInfo = sysVars.get('__pluginInfo')
49
+ assert.ok(pluginInfo.hoge !== undefined, 'キー名からプラグイン名が作られること')
50
+ assert.ok(manager.pluginfiles.hoge !== undefined)
51
+ })
52
+
53
+ it('古い形式のプラグインは nakoVersionResult が false になる', () => {
54
+ const { manager, sysVars } = createManager()
55
+ // PLUGIN_MIN_VERSION_INT(600) より小さいバージョンを指定する
56
+ manager.addPlugin({
57
+ meta: newMeta('OldPlugin', '3.5.99'),
58
+ 古い値: { type: 'const', value: 1 }
59
+ })
60
+ const pluginInfo = sysVars.get('__pluginInfo')
61
+ assert.strictEqual(pluginInfo.OldPlugin.nakoVersionResult, false)
62
+ assert.strictEqual(PLUGIN_MIN_VERSION_INT, 600)
63
+ })
64
+
65
+ it('新しい形式のプラグインは nakoVersionResult が true のまま', () => {
66
+ const { manager, sysVars } = createManager()
67
+ manager.addPlugin({
68
+ meta: newMeta('NewPlugin', '3.6.0'),
69
+ 新しい値: { type: 'const', value: 1 }
70
+ })
71
+ const pluginInfo = sysVars.get('__pluginInfo')
72
+ assert.notStrictEqual(pluginInfo.NewPlugin.nakoVersionResult, false)
73
+ })
74
+
75
+ it('「初期化」は「!プラグイン名:初期化」へ変換される', () => {
76
+ const { manager } = createManager()
77
+ const po = {
78
+ meta: newMeta('InitPlugin'),
79
+ 初期化: { type: 'func', josi: [], fn: () => {} }
80
+ }
81
+ manager.addPlugin(po)
82
+ assert.strictEqual(po['初期化'], undefined)
83
+ assert.strictEqual(typeof po['!InitPlugin:初期化'], 'object')
84
+ })
85
+
86
+ it('ファイル名に使えない文字はアンダースコアに置換される', () => {
87
+ assert.strictEqual(NakoPluginManager.removeInvalidFilenameChars('my plugin/name'), 'my_plugin_name')
88
+ // 日本語(ひらがな・カタカナ・漢字)はそのまま残る
89
+ assert.strictEqual(NakoPluginManager.removeInvalidFilenameChars('プラグイン漢字かな'), 'プラグイン漢字かな')
90
+ })
91
+
92
+ it('プラグイン名の不正文字を置換した名前で登録される', () => {
93
+ const { manager, sysVars } = createManager()
94
+ manager.addPlugin({
95
+ meta: newMeta('my plugin/name'),
96
+ 置換テスト値: { type: 'const', value: 1 }
97
+ })
98
+ const pluginInfo = sysVars.get('__pluginInfo')
99
+ assert.ok(pluginInfo.my_plugin_name !== undefined)
100
+ assert.ok(manager.modules.my_plugin_name !== undefined)
101
+ })
102
+
103
+ it('「初期化」と「!」で始まるキーはコマンド一覧に登録されない', () => {
104
+ const { manager } = createManager()
105
+ manager.addPlugin({
106
+ meta: newMeta('CommandListPlugin'),
107
+ 普通の値: { type: 'const', value: 1 },
108
+ '!クリア': { type: 'func', josi: [], fn: () => {} },
109
+ 初期化: { type: 'func', josi: [], fn: () => {} }
110
+ })
111
+ assert.strictEqual(manager.hasCommand('普通の値'), true)
112
+ assert.strictEqual(manager.hasCommand('!クリア'), false)
113
+ assert.strictEqual(manager.hasCommand('初期化'), false)
114
+ assert.strictEqual(manager.hasCommand('!CommandListPlugin:初期化'), false)
115
+ })
116
+
117
+ it('addFuncで登録した関数をgetFuncで参照できる', () => {
118
+ const { manager, sysVars } = createManager()
119
+ const fn = (a) => a
120
+ manager.addFunc('テスト関数', [['を']], fn, false)
121
+ const f = manager.getFunc('テスト関数')
122
+ assert.strictEqual(f.type, 'func')
123
+ assert.strictEqual(f.return_none, false)
124
+ assert.strictEqual(sysVars.get('テスト関数'), fn)
125
+ })
126
+
127
+ it('createFuncListFromPluginsはプラグイン由来の関数だけを返す', () => {
128
+ const { manager, sysVars } = createManager()
129
+ manager.addPlugin({
130
+ meta: newMeta('ResetPlugin'),
131
+ プラグイン値: { type: 'const', value: 1 }
132
+ })
133
+ // ユーザー定義関数を模したものはシステム領域には登録されない
134
+ const funclist = manager.createFuncListFromPlugins(sysVars)
135
+ assert.strictEqual(funclist.has('プラグイン値'), true)
136
+ assert.strictEqual(funclist.has('ユーザー関数'), false)
137
+ })
138
+
139
+ it('reset()するとユーザー定義関数は消えプラグイン関数は残る', async () => {
140
+ const nako = new NakoCompiler()
141
+ await nako.runAsync('●(Aを)ユーザー関数とは\nAを戻す\nここまで', 'main.nako3')
142
+ // ユーザー定義関数は名前空間付きで登録される
143
+ assert.ok(nako.getFunc('main__ユーザー関数') !== undefined)
144
+ nako.reset()
145
+ assert.strictEqual(nako.getFunc('main__ユーザー関数'), undefined)
146
+ assert.ok(nako.getFunc('表示') !== undefined, 'プラグインの命令は残ること')
147
+ })
148
+
149
+ it('NakoCompiler.__module と NakoPluginManager.modules は同じ内容を指す', () => {
150
+ const nako = new NakoCompiler()
151
+ nako.addPlugin({
152
+ meta: newMeta('ModuleSharePlugin'),
153
+ 共有テスト値: { type: 'const', value: 1 }
154
+ })
155
+ assert.ok(nako.__module.ModuleSharePlugin !== undefined)
156
+ assert.ok(nako.getPluginfiles().ModuleSharePlugin !== undefined)
157
+ })
158
+
159
+ it('addPluginObjectはmetaが無いプラグインに名前を付ける', () => {
160
+ const { manager, sysVars } = createManager()
161
+ manager.addPluginObject('ObjectPlugin', { オブジェクト値: { type: 'const', value: 1 } })
162
+ const pluginInfo = sysVars.get('__pluginInfo')
163
+ assert.ok(pluginInfo.ObjectPlugin !== undefined)
164
+ })
165
+
166
+ it('addPluginFromFileはメタ情報にファイルパスを記録する', () => {
167
+ const { manager, sysVars } = createManager()
168
+ manager.addPluginFromFile('/path/to/plugin.mjs', {
169
+ meta: newMeta('FilePlugin'),
170
+ ファイル値: { type: 'const', value: 1 }
171
+ })
172
+ const pluginInfo = sysVars.get('__pluginInfo')
173
+ assert.strictEqual(pluginInfo.FilePlugin.path, '/path/to/plugin.mjs')
174
+ })
175
+
176
+ it('未知のtypeを持つプラグインはエラーになる', () => {
177
+ const { manager } = createManager()
178
+ assert.throws(() => {
179
+ manager.addPlugin({
180
+ meta: newMeta('BrokenPlugin'),
181
+ 壊れた値: { type: 'unknown', value: 1 }
182
+ })
183
+ }, /プラグインの追加でエラー/)
184
+ })
185
+ })
@@ -0,0 +1,153 @@
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 { listRequireStatements, NakoRequireLoader } from '../src/nako_require.mjs'
7
+ import { NakoImportError } from '../src/nako_errors.mjs'
8
+
9
+ /**
10
+ * 取り込み文(require)の処理を NakoCompiler から分離したモジュールのテスト (#2360)
11
+ */
12
+ describe('nako_require_test', () => {
13
+ /** テスト用のローダーを作る。addPluginFromFile の呼び出し履歴も返す */
14
+ const createLoader = () => {
15
+ const nako = new NakoCompiler()
16
+ const addedPlugins = []
17
+ const loader = new NakoRequireLoader({
18
+ rawtokenize: (code, line, filename, preCode) => nako.rawtokenize(code, line, filename, preCode),
19
+ addPluginFromFile: (fpath, po) => { addedPlugins.push({ fpath, po }) },
20
+ getLogger: () => nako.getLogger(),
21
+ createScanner: () => new NakoCompiler(),
22
+ countFailure: () => {}
23
+ })
24
+ return { nako, loader, addedPlugins }
25
+ }
26
+
27
+ /** 依存ファイルの情報を作る */
28
+ const newDependency = (nako, filePath, code) => {
29
+ return {
30
+ tokens: nako.rawtokenize(code, 0, filePath),
31
+ alias: new Set([filePath]),
32
+ addPluginFile: () => {},
33
+ funclist: new Map(),
34
+ moduleExport: new Map()
35
+ }
36
+ }
37
+
38
+ it('export関数とNakoCompilerのstaticメソッドは同じ結果を返す', () => {
39
+ const nako = new NakoCompiler()
40
+ const tokens = nako.rawtokenize('!「hoge.nako3」を取り込む\n「テスト」を表示\n', 0, 'main.nako3')
41
+ const a = listRequireStatements(tokens).map((t) => t.value)
42
+ const b = NakoCompiler.listRequireStatements(tokens).map((t) => t.value)
43
+ assert.deepStrictEqual(a, ['hoge.nako3'])
44
+ assert.deepStrictEqual(a, b)
45
+ })
46
+
47
+ it('複数の取り込み文をstartの昇順で列挙する', () => {
48
+ const nako = new NakoCompiler()
49
+ const code = '!「a.nako3」を取り込む\n!「b.nako3」を取り込む\n!「c.nako3」を取り込む\n'
50
+ const tokens = nako.rawtokenize(code, 0, 'main.nako3')
51
+ const list = listRequireStatements(tokens)
52
+ assert.deepStrictEqual(list.map((t) => t.value), ['a.nako3', 'b.nako3', 'c.nako3'])
53
+ for (let i = 0; i < list.length; i++) {
54
+ assert.strictEqual(list[i].end, list[i].start + 3, '取り込み文は3トークンで構成される')
55
+ if (i > 0) {
56
+ assert.ok(list[i - 1].start < list[i].start, 'startの昇順に並んでいること')
57
+ }
58
+ }
59
+ })
60
+
61
+ it('相互に取り込み合うファイルでも無限ループしない', () => {
62
+ const { nako, loader } = createLoader()
63
+ // a.nako3 は b.nako3 を、b.nako3 は a.nako3 を取り込む
64
+ loader.dependencies = {
65
+ 'a.nako3': newDependency(nako, 'a.nako3', '!「b.nako3」を取り込む\n「A」を表示\n'),
66
+ 'b.nako3': newDependency(nako, 'b.nako3', '!「a.nako3」を取り込む\n「B」を表示\n')
67
+ }
68
+ const tokens = nako.rawtokenize('!「a.nako3」を取り込む\n「M」を表示\n', 0, 'main.nako3')
69
+ const deleted = loader.replaceRequireStatements(tokens)
70
+ assert.ok(deleted.length > 0, '取り込み文が削除されること')
71
+ assert.strictEqual(listRequireStatements(tokens).length, 0, '取り込み文がすべて置換されること')
72
+ })
73
+
74
+ it('removeRequireStatementsは取り込み文を削除する', () => {
75
+ const { nako, loader } = createLoader()
76
+ let called = 0
77
+ loader.dependencies = {
78
+ 'a.nako3': { ...newDependency(nako, 'a.nako3', '「A」を表示\n'), addPluginFile: () => { called++ } }
79
+ }
80
+ const tokens = nako.rawtokenize('!「a.nako3」を取り込む\n「M」を表示\n', 0, 'main.nako3')
81
+ const deleted = loader.removeRequireStatements(tokens)
82
+ assert.strictEqual(deleted.length, 3)
83
+ assert.strictEqual(listRequireStatements(tokens).length, 0)
84
+ assert.strictEqual(called, 1, 'シンタックスハイライトのためにaddPluginFileが呼ばれること')
85
+ })
86
+
87
+ it('読み込まれていないファイルを取り込むとエラーになる', () => {
88
+ const { nako, loader } = createLoader()
89
+ const tokens = nako.rawtokenize('!「notfound.nako3」を取り込む\n', 0, 'main.nako3')
90
+ assert.throws(() => loader.replaceRequireStatements(tokens), /読み込まれていません/)
91
+ })
92
+
93
+ it('未対応の拡張子ではNakoImportErrorになる', () => {
94
+ const { loader } = createLoader()
95
+ const tools = {
96
+ resolvePath: (name) => ({ filePath: name, type: 'txt' }),
97
+ readNako3: () => ({ task: Promise.resolve('') }),
98
+ readJs: () => ({ task: Promise.resolve(() => ({})) })
99
+ }
100
+ assert.throws(
101
+ () => loader.load('!「foo.txt」を取り込む\n', 'main.nako3', '', tools),
102
+ (err) => {
103
+ assert.ok(err instanceof NakoImportError)
104
+ assert.ok(/読み込めません/.test(err.msg))
105
+ return true
106
+ }
107
+ )
108
+ })
109
+
110
+ it('なでしこファイルを読み込んでdependenciesに保存する', async () => {
111
+ const { loader } = createLoader()
112
+ const tools = {
113
+ resolvePath: (name) => ({ filePath: name, type: 'nako3' }),
114
+ readNako3: () => ({ task: Promise.resolve('●(Aを)ライブラリ関数とは\nAを戻す\nここまで\n') }),
115
+ readJs: () => ({ task: Promise.resolve(() => ({})) })
116
+ }
117
+ await loader.load('!「lib.nako3」を取り込む\n', 'main.nako3', '', tools)
118
+ const dep = loader.dependencies['lib.nako3']
119
+ assert.ok(dep !== undefined, 'dependenciesに登録されること')
120
+ assert.ok(dep.tokens.length > 0, 'トークン列が保存されること')
121
+ const names = [...dep.funclist.keys()].filter((k) => String(k).includes('ライブラリ関数'))
122
+ assert.ok(names.length > 0, '関数名の一覧が事前に取り出されること')
123
+ })
124
+
125
+ it('JSプラグインを読み込んでaddPluginFromFileを呼ぶ', async () => {
126
+ const { loader, addedPlugins } = createLoader()
127
+ const pluginObject = {
128
+ meta: { type: 'const', value: { pluginName: 'RequireTestPlugin', nakoVersion: '3.6.0' } },
129
+ 取込テスト値: { type: 'const', value: 123 }
130
+ }
131
+ const tools = {
132
+ resolvePath: (name) => ({ filePath: name, type: 'mjs' }),
133
+ readNako3: () => ({ task: Promise.resolve('') }),
134
+ readJs: () => ({ task: Promise.resolve(() => pluginObject) })
135
+ }
136
+ await loader.load('!「plugin.mjs」を取り込む\n', 'main.nako3', '', tools)
137
+ assert.strictEqual(addedPlugins.length, 1)
138
+ assert.strictEqual(addedPlugins[0].fpath, 'plugin.mjs')
139
+ assert.strictEqual(loader.dependencies['plugin.mjs'].funclist, pluginObject)
140
+ })
141
+
142
+ it('同じファイルを2回取り込んでもエイリアスにまとめられる', async () => {
143
+ const { loader } = createLoader()
144
+ const tools = {
145
+ resolvePath: (name) => ({ filePath: 'lib.nako3', type: 'nako3' }),
146
+ readNako3: () => ({ task: Promise.resolve('「LIB」を表示\n') }),
147
+ readJs: () => ({ task: Promise.resolve(() => ({})) })
148
+ }
149
+ await loader.load('!「lib.nako3」を取り込む\n!「./lib.nako3」を取り込む\n', 'main.nako3', '', tools)
150
+ assert.strictEqual(Object.keys(loader.dependencies).length, 1)
151
+ assert.deepStrictEqual([...loader.dependencies['lib.nako3'].alias].sort(), ['./lib.nako3', 'lib.nako3'])
152
+ })
153
+ })
@@ -0,0 +1,174 @@
1
+ /* eslint-disable no-undef */
2
+ import { describe, it } from 'node:test'
3
+ import assert from 'assert'
4
+
5
+ import { NakoCompiler, newCompilerOptions } from '../src/nako3.mjs'
6
+ import { newCompilerOptions as newCompilerOptionsFromRunner } from '../src/nako_runner.mjs'
7
+
8
+ /**
9
+ * 実行部を NakoCompiler から分離したモジュールのテスト (#2360)
10
+ */
11
+ describe('nako_runner_test', () => {
12
+ it('newCompilerOptionsは既定値を埋める', () => {
13
+ const opt = newCompilerOptions()
14
+ assert.strictEqual(opt.testOnly, false)
15
+ assert.strictEqual(opt.resetEnv, false)
16
+ assert.strictEqual(opt.resetAll, false)
17
+ assert.strictEqual(opt.preCode, '')
18
+ assert.strictEqual(opt.nakoGlobal, null)
19
+ })
20
+
21
+ it('newCompilerOptionsは指定した値を残す', () => {
22
+ const opt = newCompilerOptions({ testOnly: true, preCode: 'A=1;' })
23
+ assert.strictEqual(opt.testOnly, true)
24
+ assert.strictEqual(opt.preCode, 'A=1;')
25
+ assert.strictEqual(opt.resetEnv, false)
26
+ })
27
+
28
+ it('nako3.mjsとnako_runner.mjsのnewCompilerOptionsは同じ関数', () => {
29
+ assert.strictEqual(newCompilerOptions, newCompilerOptionsFromRunner)
30
+ })
31
+
32
+ it('runAsyncが実行環境を返し__globalObjに記録される', async () => {
33
+ const nako = new NakoCompiler()
34
+ const g = await nako.runAsync('「テスト」を表示', 'main.nako3')
35
+ assert.strictEqual(g.log, 'テスト')
36
+ assert.strictEqual(nako.__globalObj, g, '現在の実行環境が記録されること')
37
+ assert.strictEqual(nako.__globals.length, 1)
38
+ assert.strictEqual(nako.__globals[0], g)
39
+ })
40
+
41
+ it('runAsyncは非同期プログラムの実行完了を待つ (#2381)', async () => {
42
+ const nako = new NakoCompiler()
43
+ const g = await nako.runAsync('0.02秒待つ\n「完了」を表示', 'main.nako3')
44
+ assert.strictEqual(g.log, '完了')
45
+ })
46
+
47
+ it('非同期処理中のエラーがrunAsync完了時に反映される (#2381)', async () => {
48
+ const nako = new NakoCompiler()
49
+ const g = await nako.runAsync('0.01秒待つ\n「ぐぬ」のエラー発生', 'main.nako3')
50
+ assert.strictEqual(g.numFailures, 1)
51
+ })
52
+
53
+ it('__globalObjは代入もできる(後方互換)', () => {
54
+ const nako = new NakoCompiler()
55
+ assert.strictEqual(nako.__globalObj, null)
56
+ const dummy = { dummy: true }
57
+ nako.__globalObj = dummy
58
+ assert.strictEqual(nako.__globalObj, dummy)
59
+ })
60
+
61
+ it('__globalsは代入もできる(後方互換)', () => {
62
+ const nako = new NakoCompiler()
63
+ nako.__globals = []
64
+ assert.deepStrictEqual(nako.__globals, [])
65
+ })
66
+
67
+ it('連続して実行すると同じ実行環境を再利用する', async () => {
68
+ const nako = new NakoCompiler()
69
+ const g1 = await nako.runAsync('A=10', 'main.nako3')
70
+ const g2 = await nako.runAsync('Aを表示', 'main.nako3')
71
+ assert.strictEqual(g1, g2, '実行環境が共有されること')
72
+ assert.strictEqual(g2.log, '10')
73
+ assert.strictEqual(nako.__globals.length, 1)
74
+ })
75
+
76
+ it('clearPluginsで実行環境の一覧が空になる', async () => {
77
+ const nako = new NakoCompiler()
78
+ await nako.runAsync('「テスト」を表示', 'main.nako3')
79
+ assert.strictEqual(nako.__globals.length, 1)
80
+ nako.clearPlugins()
81
+ assert.strictEqual(nako.__globals.length, 0)
82
+ })
83
+
84
+ it('resetAllを指定すると新しい実行環境が作られる', async () => {
85
+ const nako = new NakoCompiler()
86
+ const g1 = await nako.runAsync('A=10', 'main.nako3')
87
+ const g2 = await nako.runAsync('「テスト」を表示', 'main.nako3', newCompilerOptions({ resetAll: true, resetEnv: true }))
88
+ assert.notStrictEqual(g1, g2, '実行環境が作り直されること')
89
+ })
90
+
91
+ it('nakoGlobalを指定すると、その実行環境を使う', async () => {
92
+ const nako = new NakoCompiler()
93
+ const g1 = await nako.runAsync('A=10', 'main.nako3')
94
+ const g2 = await nako.runAsync('Aを表示', 'main.nako3', newCompilerOptions({ nakoGlobal: g1 }))
95
+ assert.strictEqual(g1, g2)
96
+ assert.strictEqual(g2.log, '10')
97
+ })
98
+
99
+ it('runSyncも同じように動作する', () => {
100
+ const nako = new NakoCompiler()
101
+ const g = nako.runSync('「同期」を表示', 'main.nako3')
102
+ assert.strictEqual(g.log, '同期')
103
+ })
104
+
105
+ it('testメソッドはpreCodeを反映する', () => {
106
+ const nako = new NakoCompiler()
107
+ // preCode は code の先頭に含めて渡す仕様
108
+ const g = nako.test('A=5;Aを表示', 'main.nako3', 'A=5;')
109
+ assert.strictEqual(g.log, '5')
110
+ })
111
+
112
+ it('runReset は他の実行インスタンスもリセットする', async () => {
113
+ const nako = new NakoCompiler()
114
+ await nako.runAsync('A=10', 'main.nako3')
115
+ const g = await nako.runReset('「リセット」を表示', 'main.nako3')
116
+ assert.strictEqual(g.log, 'リセット')
117
+ assert.strictEqual(nako.__globals.length, 1, '古い実行環境が破棄されること')
118
+ })
119
+
120
+ it('実行時エラーはログに記録された上で例外になる', async () => {
121
+ const nako = new NakoCompiler()
122
+ await assert.rejects(async () => {
123
+ await nako.runAsync('『存在しない関数』のエラー発生', 'main.nako3')
124
+ })
125
+ })
126
+
127
+ it('初期化に失敗したプラグインのクリア関数は呼ばれない #2064', async () => {
128
+ // プラグインの初期化は生成されたJavaScriptの中で実行されるため、
129
+ // 実行に失敗すると初期化されないまま実行環境が残ってしまう。
130
+ // その状態で次の実行を行うと「!クリア」でエラーが出ていた。
131
+ let failInit = true
132
+ let clearCount = 0
133
+ const plugin = {
134
+ meta: { type: 'const', value: { pluginName: 'plugin_dummy2064', nakoVersion: '3.6.0' } },
135
+ 初期化: {
136
+ type: 'func',
137
+ josi: [],
138
+ pure: true,
139
+ fn: (sys) => {
140
+ if (failInit) { throw new Error('初期化に失敗') }
141
+ sys.__dummyClear2064 = () => { clearCount++ }
142
+ }
143
+ },
144
+ '!クリア': {
145
+ type: 'func',
146
+ josi: [],
147
+ pure: true,
148
+ // 初期化されていなければ、ここで TypeError になる
149
+ fn: (sys) => { sys.__dummyClear2064() }
150
+ }
151
+ }
152
+ const errors = []
153
+ const nako = new NakoCompiler()
154
+ nako.getLogger().addListener('error', (data) => { errors.push(data.noColor) })
155
+ nako.addPluginObject('plugin_dummy2064', plugin)
156
+
157
+ // 1回目 … 初期化に失敗して実行環境だけが残る
158
+ await assert.rejects(async () => { await nako.runReset('「NG」を表示', 'main.nako3') })
159
+
160
+ // 2回目 … 初期化されていないのでクリア関数は呼ばれない
161
+ failInit = false
162
+ const g2 = await nako.runReset('「OK」を表示', 'main.nako3')
163
+ assert.strictEqual(g2.log, 'OK')
164
+ assert.strictEqual(clearCount, 0, 'クリア関数が呼ばれないこと')
165
+ assert.strictEqual(
166
+ errors.filter((e) => e.includes('クリア関数でエラーが発生しました')).length, 0,
167
+ 'クリア関数のエラーが出ないこと')
168
+
169
+ // 3回目 … 2回目で初期化が成功しているのでクリア関数が呼ばれる
170
+ const g3 = await nako.runReset('「OK2」を表示', 'main.nako3')
171
+ assert.strictEqual(g3.log, 'OK2')
172
+ assert.strictEqual(clearCount, 1, '初期化済みならクリア関数が呼ばれること')
173
+ })
174
+ })
@@ -0,0 +1,115 @@
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 { NakoTokenizer } from '../src/nako_tokenizer.mjs'
7
+ import { NakoLogger } from '../src/nako_logger.mjs'
8
+
9
+ /**
10
+ * 字句解析パイプラインを NakoCompiler から分離したモジュールのテスト (#2360)
11
+ */
12
+ describe('nako_tokenizer_test', () => {
13
+ /** 取り込み文を扱わない、単体テスト用のトークナイザを作る */
14
+ const createTokenizer = () => {
15
+ const logger = new NakoLogger()
16
+ return new NakoTokenizer({
17
+ getLogger: () => logger,
18
+ // 取り込み文は扱わないので、何もせず空配列を返す
19
+ replaceRequireStatements: () => [],
20
+ removeRequireStatements: () => []
21
+ })
22
+ }
23
+
24
+ it('rawtokenizeがトークン列を返す', () => {
25
+ const tokenizer = createTokenizer()
26
+ // 単体のトークナイザは関数一覧を持たないため、命令は word になる
27
+ const tokens = tokenizer.rawtokenize('1と2を足す', 0, 'main.nako3')
28
+ const types = tokens.map((t) => t.type)
29
+ assert.ok(types.includes('number'), '数値トークンが含まれること')
30
+ assert.ok(types.includes('word'), '単語トークンが含まれること')
31
+ assert.deepStrictEqual(tokens.filter((t) => t.type === 'number').map((t) => t.value), [1, 2])
32
+ })
33
+
34
+ it('rawtokenizeはpreCodeがcodeの先頭にないとエラーになる', () => {
35
+ const tokenizer = createTokenizer()
36
+ assert.throws(
37
+ () => tokenizer.rawtokenize('「A」を表示', 0, 'main.nako3', '「B」を表示'),
38
+ /preCodeを含める必要があります/
39
+ )
40
+ })
41
+
42
+ it('rawtokenizeがモジュールリストへ自身を追加する', () => {
43
+ const tokenizer = createTokenizer()
44
+ tokenizer.rawtokenize('「A」を表示', 0, 'sample.nako3')
45
+ assert.ok(tokenizer.getModList().includes('sample'))
46
+ })
47
+
48
+ it('全角の記号が半角へ正規化される', () => {
49
+ const tokenizer = createTokenizer()
50
+ // 全角の「=」が半角の「=」として解釈される
51
+ const tokens = tokenizer.rawtokenize('A=1', 0, 'main.nako3')
52
+ const types = tokens.map((t) => t.type)
53
+ assert.ok(types.includes('eq'), '代入のトークンになること')
54
+ })
55
+
56
+ it('トークンに行番号と桁位置が設定される', () => {
57
+ const tokenizer = createTokenizer()
58
+ const tokens = tokenizer.rawtokenize('「A」を表示\n「B」を表示\n', 0, 'main.nako3')
59
+ const second = tokens.filter((t) => t.type === 'string' && t.value === 'B')[0]
60
+ assert.ok(second !== undefined)
61
+ assert.strictEqual(second.line, 1, '2行目のトークンの行番号は1になること')
62
+ })
63
+
64
+ it('コメントのトークンが残る', () => {
65
+ const tokenizer = createTokenizer()
66
+ const tokens = tokenizer.rawtokenize('# これはコメント\n「A」を表示\n', 0, 'main.nako3')
67
+ const comments = tokens.filter((t) => t.type === 'line_comment')
68
+ assert.strictEqual(comments.length, 1)
69
+ })
70
+
71
+ it('lexCodeTokenがstartOffsetを加算する', () => {
72
+ const tokenizer = createTokenizer()
73
+ const res = tokenizer.lexCodeToken('1に2を足す', 0, 'main.nako3', 100)
74
+ const withOffset = res.tokens.filter((t) => t.startOffset !== undefined)
75
+ assert.ok(withOffset.length > 0)
76
+ assert.ok(withOffset.every((t) => t.startOffset >= 100), 'startOffsetが加算されること')
77
+ })
78
+
79
+ it('lexCodeTokenにstartOffsetがnullならオフセットを消す', () => {
80
+ const tokenizer = createTokenizer()
81
+ const res = tokenizer.lexCodeToken('1に2を足す', 0, 'main.nako3', null)
82
+ assert.ok(res.tokens.every((t) => t.startOffset === undefined))
83
+ assert.ok(res.tokens.every((t) => t.endOffset === undefined))
84
+ })
85
+
86
+ it('lexが字句解析の結果を返す', () => {
87
+ const tokenizer = createTokenizer()
88
+ const res = tokenizer.lex('# コメント\n「A」を表示\n', 'main.nako3')
89
+ assert.ok(res.tokens.length > 0)
90
+ assert.strictEqual(res.commentTokens.length, 1)
91
+ assert.deepStrictEqual(res.requireTokens, [])
92
+ })
93
+
94
+ it('NakoCompilerの各メソッドはトークナイザへ委譲される', () => {
95
+ const nako = new NakoCompiler()
96
+ const viaCompiler = nako.rawtokenize('1と2を足す', 0, 'main.nako3')
97
+ const viaTokenizer = nako.tokenizer.rawtokenize('1と2を足す', 0, 'main.nako3')
98
+ assert.deepStrictEqual(viaCompiler.map((t) => t.type), viaTokenizer.map((t) => t.type))
99
+ // getModList もトークナイザが持つモジュールリストを返す
100
+ assert.strictEqual(nako.getModList(), nako.tokenizer.getModList())
101
+ })
102
+
103
+ it('replaceLoggerを呼ぶとトークナイザのロガーも差し替わる', () => {
104
+ const nako = new NakoCompiler()
105
+ const logger = nako.replaceLogger()
106
+ assert.strictEqual(nako.tokenizer.lexer.logger, logger)
107
+ assert.strictEqual(nako.getLogger(), logger)
108
+ })
109
+
110
+ it('文字列展開の中のコードも字句解析される', async () => {
111
+ const nako = new NakoCompiler()
112
+ const g = await nako.runAsync('A=3;「値は{A}です」を表示', 'main.nako3')
113
+ assert.strictEqual(g.log, '値は3です')
114
+ })
115
+ })