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,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
+ })
@@ -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,114 @@
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('__globalObjは代入もできる(後方互換)', () => {
42
+ const nako = new NakoCompiler()
43
+ assert.strictEqual(nako.__globalObj, null)
44
+ const dummy = { dummy: true }
45
+ nako.__globalObj = dummy
46
+ assert.strictEqual(nako.__globalObj, dummy)
47
+ })
48
+
49
+ it('__globalsは代入もできる(後方互換)', () => {
50
+ const nako = new NakoCompiler()
51
+ nako.__globals = []
52
+ assert.deepStrictEqual(nako.__globals, [])
53
+ })
54
+
55
+ it('連続して実行すると同じ実行環境を再利用する', async () => {
56
+ const nako = new NakoCompiler()
57
+ const g1 = await nako.runAsync('A=10', 'main.nako3')
58
+ const g2 = await nako.runAsync('Aを表示', 'main.nako3')
59
+ assert.strictEqual(g1, g2, '実行環境が共有されること')
60
+ assert.strictEqual(g2.log, '10')
61
+ assert.strictEqual(nako.__globals.length, 1)
62
+ })
63
+
64
+ it('clearPluginsで実行環境の一覧が空になる', async () => {
65
+ const nako = new NakoCompiler()
66
+ await nako.runAsync('「テスト」を表示', 'main.nako3')
67
+ assert.strictEqual(nako.__globals.length, 1)
68
+ nako.clearPlugins()
69
+ assert.strictEqual(nako.__globals.length, 0)
70
+ })
71
+
72
+ it('resetAllを指定すると新しい実行環境が作られる', async () => {
73
+ const nako = new NakoCompiler()
74
+ const g1 = await nako.runAsync('A=10', 'main.nako3')
75
+ const g2 = await nako.runAsync('「テスト」を表示', 'main.nako3', newCompilerOptions({ resetAll: true, resetEnv: true }))
76
+ assert.notStrictEqual(g1, g2, '実行環境が作り直されること')
77
+ })
78
+
79
+ it('nakoGlobalを指定すると、その実行環境を使う', async () => {
80
+ const nako = new NakoCompiler()
81
+ const g1 = await nako.runAsync('A=10', 'main.nako3')
82
+ const g2 = await nako.runAsync('Aを表示', 'main.nako3', newCompilerOptions({ nakoGlobal: g1 }))
83
+ assert.strictEqual(g1, g2)
84
+ assert.strictEqual(g2.log, '10')
85
+ })
86
+
87
+ it('runSyncも同じように動作する', () => {
88
+ const nako = new NakoCompiler()
89
+ const g = nako.runSync('「同期」を表示', 'main.nako3')
90
+ assert.strictEqual(g.log, '同期')
91
+ })
92
+
93
+ it('testメソッドはpreCodeを反映する', () => {
94
+ const nako = new NakoCompiler()
95
+ // preCode は code の先頭に含めて渡す仕様
96
+ const g = nako.test('A=5;Aを表示', 'main.nako3', 'A=5;')
97
+ assert.strictEqual(g.log, '5')
98
+ })
99
+
100
+ it('runReset は他の実行インスタンスもリセットする', async () => {
101
+ const nako = new NakoCompiler()
102
+ await nako.runAsync('A=10', 'main.nako3')
103
+ const g = await nako.runReset('「リセット」を表示', 'main.nako3')
104
+ assert.strictEqual(g.log, 'リセット')
105
+ assert.strictEqual(nako.__globals.length, 1, '古い実行環境が破棄されること')
106
+ })
107
+
108
+ it('実行時エラーはログに記録された上で例外になる', async () => {
109
+ const nako = new NakoCompiler()
110
+ await assert.rejects(async () => {
111
+ await nako.runAsync('『存在しない関数』のエラー発生', 'main.nako3')
112
+ })
113
+ })
114
+ })