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
@@ -143,6 +143,19 @@ export class NakoGlobal {
143
143
  this.logger.stdout(text)
144
144
  }
145
145
 
146
+ /**
147
+ * プラグインの初期化が完了しているかどうかを調べる (#2064)
148
+ * @param pname プラグイン名
149
+ * @param po プラグイン・オブジェクト
150
+ */
151
+ private isPluginInitialized(pname: string, po: {[key: string]: any}): boolean {
152
+ const initKey = `!${pname}:初期化`
153
+ // 初期化関数を持たないプラグインは、常に初期化済みとみなす
154
+ if (!po[initKey]) { return true }
155
+ // 初期化関数は生成されたJavaScriptの中で実行され、完了すると「初期化済」が設定される
156
+ return this.__varslist[0].has(`${initKey}済`)
157
+ }
158
+
146
159
  /**
147
160
  * 毎プラグインの「!クリア」関数を実行
148
161
  */
@@ -152,6 +165,11 @@ export class NakoGlobal {
152
165
  const clearName = '!クリア'
153
166
  for (const pname in this.pluginfiles) {
154
167
  const po = this.__module[pname]
168
+ if (!po) { continue }
169
+ // 初期化されていないプラグインのクリア関数は呼ばない (#2064)
170
+ // (メモ) プラグインの初期化は生成されたJavaScriptの中で行われるため、
171
+ // コンパイルや実行に失敗すると初期化されないまま実行環境が残ることがある。
172
+ if (!this.isPluginInitialized(pname, po)) { continue }
155
173
  if (po[clearName] && po[clearName].fn) {
156
174
  try {
157
175
  po[clearName].fn(this)
@@ -221,7 +221,7 @@ const INDENT_MODE_KEYWORDS = ['!インデント構文', '!ここまでだるい'
221
221
  /** インデント構文 --- インデントを見て"ここまで"を自動挿入 (#596) */
222
222
  export function convertIndentSyntax(tokens: Token[]): Token[] {
223
223
  // インデント構文の変換が必要か?
224
- if (!useIndentSynax(tokens)) { return tokens }
224
+ if (!useIndentSyntax(tokens)) { return tokens }
225
225
  // 『ここまで』があったらエラーを出す
226
226
  for (const t of tokens) {
227
227
  if (t.type === 'ここまで') {
@@ -321,7 +321,7 @@ export function convertIndentSyntax(tokens: Token[]): Token[] {
321
321
  return result
322
322
  }
323
323
 
324
- function useIndentSynax(tokens: Token[]) : boolean {
324
+ function useIndentSyntax(tokens: Token[]) : boolean {
325
325
  // インデント構文が必要かチェック (最初の100個をチェック)
326
326
  for (let i = 0; i < tokens.length; i++) {
327
327
  if (i > 100) { break }
@@ -42,7 +42,7 @@ export const rules: NakoLexRule[] = [
42
42
  { name: 'eol', pattern: /^\n/ },
43
43
  { name: 'eol', pattern: /^;/ },
44
44
  // eslint-disable-next-line no-irregular-whitespace
45
- { name: 'space', pattern: /^(\x20|\x09| |・|⎿ |└||)+/ }, // #877,#1015
45
+ { name: 'space', pattern: /^(\x20|\t| |・|⎿ |└||)+/ }, // #877,#1015
46
46
  { name: 'comma', pattern: /^,/ },
47
47
  { name: 'line_comment', pattern: /^#[^\n]*/ },
48
48
  { name: 'line_comment', pattern: /^\/\/[^\n]*/ },
@@ -1,12 +1,50 @@
1
1
 
2
2
  /**
3
3
  * nadesiko v3 parser
4
+ *
5
+ * 再帰下降パーサ。トークン列を読み進めながら構文木(Ast)を組み立てる。
6
+ * 各構文規則は `yXxx` という名前のメソッドで、カーソル(this.index)と
7
+ * 計算用スタック(this.stack)を共有しながら相互に呼び合う。
8
+ * カーソル操作・スタック操作・変数検索は基底クラス NakoParserBase にある。
9
+ *
10
+ * ## 構文規則の呼び出し関係 (#2364)
11
+ *
12
+ * ```
13
+ * parse
14
+ * └ startParser → ySentenceList → ySentence ─┬ yIF / yAtohantei / yTryExcept / yDebugPrint
15
+ * ├ yDNCLMode / ySetGenMode / ySpeedMode ...
16
+ * ├ yLet ── yLetArrayAt / yLetArrayBracket
17
+ * ├ yDefFunc / yDefTest → yDefFuncCommon
18
+ * └ yCall ─┬ yDainyu / ySadameru / yIncDec
19
+ * ├ yRepeatTime / yWhile / yFor /
20
+ * │ yForEach / ySwitch / yReturn
21
+ * └ yCallFunc → yMumeiFunc
22
+ *
23
+ * yCalc → yGetArg ─┬ yRange
24
+ * ├ yGetArgOperator → infixToAST() … nako_parser_operator.mts
25
+ * └ yValue ─┬ yValueKakko / yValueWord / yMumeiFunc
26
+ * └ yJSONArray / yJSONObject → yJSONArrayValue /
27
+ * yJSONObjectValue → yCalc
28
+ * ```
29
+ *
30
+ * 上の図は主要な流れだけを示したもので、実際には
31
+ * `ySentence ↔ yBlock ↔ yCall ↔ yCalc ↔ yValue ↔ yJSON*` が
32
+ * 大きな相互再帰を成している。この相互再帰があるため、構文規則そのものは
33
+ * ファイルを分けても結合が下がらない。分離してあるのは、
34
+ * 相互再帰の外側にあってカーソルにもスタックにも触れないものだけ。
35
+ *
36
+ * - `nako_parser_operator.mts` … 演算子の優先順位による構文木の組み立て
37
+ * - `nako_parser_async.mts` … 解析後に asyncFn を伝播させる後処理パス
38
+ * - `nako_parser_message.mts` … エラーメッセージの組み立て
4
39
  */
5
40
  import { opPriority, RenbunJosi, operatorList } from './nako_parser_const.mjs'
6
41
  import { NakoParserBase } from './nako_parser_base.mjs'
42
+ import { infixToAST } from './nako_parser_operator.mjs'
43
+ import { checkAsyncFn } from './nako_parser_async.mjs'
44
+ import { makeStackBalanceReport } from './nako_parser_message.mjs'
7
45
  import { NakoSyntaxError } from './nako_errors.mjs'
8
46
  import { NakoLexer } from './nako_lexer.mjs'
9
- import { FuncListItemType, FuncArgs, NewEmptyToken, SourceMap } from './nako_types.mjs'
47
+ import { FuncListItemType, NewEmptyToken, SourceMap } from './nako_types.mjs'
10
48
  import { NodeType, Ast, AstEol, AstBlocks, AstOperator, AstConst, AstLet, AstLetArray, AstIf, AstWhile, AstAtohantei, AstFor, AstForeach, AstSwitch, AstRepeatTimes, AstDefFunc, AstCallFunc, AstStrValue, AstDefVar, AstDefVarList } from './nako_ast.mjs'
11
49
  import { Token, TokenDefFunc, TokenCallFunc } from './nako_token.mjs'
12
50
 
@@ -27,12 +65,8 @@ export class NakoParser extends NakoParserBase {
27
65
  const result = this.startParser()
28
66
 
29
67
  // 関数毎に非同期処理が必要かどうかを判定する
30
- this.isModifiedNodes = false
31
- this._checkAsyncFn(result)
32
- while (this.isModifiedNodes) {
33
- this.isModifiedNodes = false
34
- this._checkAsyncFn(result)
35
- }
68
+ // 「非同期関数を呼ぶ関数もまた非同期」と伝播していくので、変化が無くなるまで繰り返す
69
+ while (checkAsyncFn(result, this.funclist)) { /* 変化が無くなるまで繰り返す */ }
36
70
 
37
71
  return result
38
72
  }
@@ -79,34 +113,13 @@ export class NakoParser extends NakoParserBase {
79
113
  return { type: 'block', blocks, josi: '', ...map, end: this.peekSourceMap() }
80
114
  }
81
115
 
82
- /** 余剰スタックのレポートを作る */
116
+ /** 余剰スタックのレポートを作る
117
+ * (レポートの組み立ては nako_parser_message.mts の makeStackBalanceReport) #2364
118
+ */
83
119
  makeStackBalanceReport(): string {
84
- const words: string[] = []
85
- this.stack.forEach((t) => {
86
- let w = this.nodeToStr(t, { depth: 1 }, false)
87
- if (t.josi) { w += t.josi }
88
- words.push(w)
89
- })
90
- const desc = words.join(',')
91
- // 最近使った関数の使い方レポートを作る #1093
92
- let descFunc = ''
93
- const chA = 'A'.charCodeAt(0)
94
- for (const f of this.recentlyCalledFunc) {
95
- descFunc += ' - '
96
- let no = 0
97
- const josiA: FuncArgs | undefined = (f).josi
98
- if (josiA) {
99
- for (const arg of josiA) {
100
- const ch = String.fromCharCode(chA + no)
101
- descFunc += ch
102
- if (arg.length === 1) { descFunc += arg[0] } else { descFunc += `(${arg.join('|')})` }
103
- no++
104
- }
105
- }
106
- descFunc += String(f.name) + '\n'
107
- }
120
+ const report = makeStackBalanceReport(this.stack, this.recentlyCalledFunc)
108
121
  this.recentlyCalledFunc = []
109
- return `未解決の単語があります: [${desc}]\n次の命令の可能性があります:\n${descFunc}`
122
+ return report
110
123
  }
111
124
 
112
125
  yEOL(): AstEol | null {
@@ -196,6 +209,10 @@ export class NakoParser extends NakoParserBase {
196
209
  return null
197
210
  }
198
211
 
212
+ // ---------------------------------------------------------------------------
213
+ // 実行モードの指定と廃止された構文
214
+ // ---------------------------------------------------------------------------
215
+
199
216
  /** [廃止] 非同期モード #11 @returns {Ast} */
200
217
  yASyncMode(): Ast {
201
218
  this.logger.error('『非同期モード』構文は廃止されました(https://nadesi.com/v3/doc/go.php?1028)。', this.peek())
@@ -240,6 +257,10 @@ export class NakoParser extends NakoParserBase {
240
257
  return { type: 'run_mode', value: mode, ...map, end: this.peekSourceMap() }
241
258
  }
242
259
 
260
+ // ---------------------------------------------------------------------------
261
+ // ブロックと関数定義
262
+ // ---------------------------------------------------------------------------
263
+
243
264
  /** @returns {AstBlocks} */
244
265
  yBlock(): AstBlocks {
245
266
  const map = this.peekSourceMap()
@@ -387,6 +408,10 @@ export class NakoParser extends NakoParserBase {
387
408
  }
388
409
  }
389
410
 
411
+ // ---------------------------------------------------------------------------
412
+ // 条件分岐(もし文)
413
+ // ---------------------------------------------------------------------------
414
+
390
415
  /** 「もし」文の条件を取得 */
391
416
  yIFCond(): Ast {
392
417
  const map = this.peekSourceMap()
@@ -534,6 +559,10 @@ export class NakoParser extends NakoParserBase {
534
559
  }
535
560
  }
536
561
 
562
+ // ---------------------------------------------------------------------------
563
+ // 実行速度・パフォーマンスの指定
564
+ // ---------------------------------------------------------------------------
565
+
537
566
  ySpeedMode(): AstBlocks | null {
538
567
  const map: SourceMap = this.peekSourceMap()
539
568
  if (!this.check2(['string', '実行速度優先'])) {
@@ -649,6 +678,10 @@ export class NakoParser extends NakoParserBase {
649
678
  return { type: 'eol', ...this.peekSourceMap(), end: this.peekSourceMap() }
650
679
  }
651
680
 
681
+ // ---------------------------------------------------------------------------
682
+ // 引数の取得と演算子
683
+ // ---------------------------------------------------------------------------
684
+
652
685
  /**
653
686
  * 1つ目の値を与え、その後に続く計算式を取得し、優先規則に沿って並び替えして戻す
654
687
  * @param {Ast} firstValue
@@ -675,7 +708,7 @@ export class NakoParser extends NakoParserBase {
675
708
  }
676
709
  if (args.length === 0) { return null }
677
710
  if (args.length === 1) { return args[0] }
678
- return this.infixToAST(args)
711
+ return infixToAST(args, this.logger)
679
712
  }
680
713
 
681
714
  /**
@@ -743,76 +776,6 @@ export class NakoParser extends NakoParserBase {
743
776
  return this.yGetArgOperator(value1)
744
777
  }
745
778
 
746
- infixToPolish(list: Ast[]): Ast[] {
747
- // 中間記法から逆ポーランドに変換
748
- const priority = (t: Ast) => {
749
- if (opPriority[t.type]) { return opPriority[t.type] }
750
- return 10
751
- }
752
- const stack: Ast[] = []
753
- const polish: Ast[] = []
754
- while (list.length > 0) {
755
- const t = list.shift()
756
- if (!t) { break }
757
- while (stack.length > 0) { // 優先順位を見て移動する
758
- const sTop = stack[stack.length - 1]
759
- if (priority(t) > priority(sTop)) { break }
760
- const tpop = stack.pop()
761
- if (!tpop) {
762
- this.logger.error('計算式に間違いがあります。', t)
763
- break
764
- }
765
- polish.push(tpop)
766
- }
767
- stack.push(t)
768
- }
769
- // 残った要素を積み替える
770
- while (stack.length > 0) {
771
- const t = stack.pop()
772
- if (t) { polish.push(t) }
773
- }
774
- return polish
775
- }
776
-
777
- /** @returns {Ast | null} */
778
- infixToAST(list: Ast[]): Ast | null {
779
- if (list.length === 0) { return null }
780
- // 逆ポーランドを構文木に
781
- const josi = list[list.length - 1].josi
782
- const node = list[list.length - 1]
783
- const polish = this.infixToPolish(list)
784
- /** @type {Ast[]} */
785
- const stack = []
786
- for (const t of polish) {
787
- if (!opPriority[t.type]) { // 演算子ではない
788
- stack.push(t)
789
- continue
790
- }
791
- const b:Ast|undefined = stack.pop()
792
- const a:Ast|undefined = stack.pop()
793
- if (a === undefined || b === undefined) {
794
- this.logger.debug('--- 計算式(逆ポーランド) ---\n' + JSON.stringify(polish))
795
- throw NakoSyntaxError.fromNode('計算式でエラー', node)
796
- }
797
- /** @type {AstOperator} */
798
- const op: AstOperator = {
799
- type: 'op',
800
- operator: t.type,
801
- blocks: [a, b],
802
- josi,
803
- startOffset: a.startOffset,
804
- endOffset: a.endOffset,
805
- line: a.line,
806
- column: a.column,
807
- file: a.file
808
- }
809
- stack.push(op)
810
- }
811
- const ans = stack.pop()
812
- if (!ans) { return null }
813
- return ans
814
- }
815
-
816
779
  yGetArgParen(y: Ast[], funcName?: string): Ast[] { // C言語風呼び出しでカッコの中を取得
817
780
  let isClose = false
818
781
  const si = this.stack.length
@@ -867,6 +830,10 @@ export class NakoParser extends NakoParserBase {
867
830
  return node
868
831
  }
869
832
 
833
+ // ---------------------------------------------------------------------------
834
+ // 繰り返しと条件分岐の各構文
835
+ // ---------------------------------------------------------------------------
836
+
870
837
  /** @returns {AstRepeatTimes | null} */
871
838
  yRepeatTime(): AstRepeatTimes | null {
872
839
  const map = this.peekSourceMap()
@@ -1202,6 +1169,10 @@ export class NakoParser extends NakoParserBase {
1202
1169
  return ast
1203
1170
  }
1204
1171
 
1172
+ // ---------------------------------------------------------------------------
1173
+ // 無名関数
1174
+ // ---------------------------------------------------------------------------
1175
+
1205
1176
  /** 無名関数
1206
1177
  * @returns {AstDefFunc|null}
1207
1178
  */
@@ -1263,6 +1234,10 @@ export class NakoParser extends NakoParserBase {
1263
1234
  }
1264
1235
  }
1265
1236
 
1237
+ // ---------------------------------------------------------------------------
1238
+ // 代入文と増減
1239
+ // ---------------------------------------------------------------------------
1240
+
1266
1241
  /** 代入構文 */
1267
1242
  yDainyu(): AstBlocks | null {
1268
1243
  const map = this.peekSourceMap()
@@ -1380,6 +1355,10 @@ export class NakoParser extends NakoParserBase {
1380
1355
  }
1381
1356
  }
1382
1357
 
1358
+ // ---------------------------------------------------------------------------
1359
+ // 関数呼び出し
1360
+ // ---------------------------------------------------------------------------
1361
+
1383
1362
  yCall(): Ast | null {
1384
1363
  if (this.isEOF()) { return null }
1385
1364
 
@@ -1577,6 +1556,10 @@ export class NakoParser extends NakoParserBase {
1577
1556
  return null
1578
1557
  }
1579
1558
 
1559
+ // ---------------------------------------------------------------------------
1560
+ // 代入・変数定義と配列要素への代入
1561
+ // ---------------------------------------------------------------------------
1562
+
1580
1563
  /** @returns {Ast | null} */
1581
1564
  yLet(): AstBlocks | null {
1582
1565
  const map = this.peekSourceMap()
@@ -2117,7 +2100,7 @@ export class NakoParser extends NakoParserBase {
2117
2100
  } as AstLet
2118
2101
  }
2119
2102
  // 二次元配列 + オブジェクトプロパティ構文 --- word[a, b]$c = d
2120
- if (this.accept(['word', '[', this.yCalc, ',', this.yCalc, ']', '$', 'word', 'eq', this.yCalc])) {
2103
+ if (this.accept(['word', '[', this.yCalc, 'comma', this.yCalc, ']', '$', 'word', 'eq', this.yCalc])) {
2121
2104
  const astValue = this.y[9]
2122
2105
  const astIndexes = this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])])
2123
2106
  const astProp = this.y[7]
@@ -2157,6 +2140,10 @@ export class NakoParser extends NakoParserBase {
2157
2140
  return null
2158
2141
  }
2159
2142
 
2143
+ // ---------------------------------------------------------------------------
2144
+ // 計算式と値
2145
+ // ---------------------------------------------------------------------------
2146
+
2160
2147
  /** @returns {Ast | null} */
2161
2148
  yCalc(): Ast|null {
2162
2149
  const map = this.peekSourceMap()
@@ -2180,6 +2167,10 @@ export class NakoParser extends NakoParserBase {
2180
2167
  let fCalc:Ast = t1
2181
2168
  // それが連文か助詞を読んで確認
2182
2169
  if (RenbunJosi.indexOf(t1.josi || '') >= 0) {
2170
+ // 『(式)して戻す』のように、式の途中に『戻す』文は書けない (#2064)
2171
+ if (this.check('戻る')) {
2172
+ throw NakoSyntaxError.fromNode('式の中で『戻す(戻る)』文は使えません。『(値)を(変数)に代入』などで一度値を受け取ってから『(変数)を戻す』と書いてください。', map)
2173
+ }
2183
2174
  // 連文なら右側を読んで左側とくっつける
2184
2175
  const t2 = this.yCalc()
2185
2176
  if (t2) {
@@ -2573,6 +2564,10 @@ export class NakoParser extends NakoParserBase {
2573
2564
  return null
2574
2565
  }
2575
2566
 
2567
+ // ---------------------------------------------------------------------------
2568
+ // 変数名の解決と登録
2569
+ // ---------------------------------------------------------------------------
2570
+
2576
2571
  /** 変数を生成 */
2577
2572
  createVar(word: Token|Ast, isConst: boolean, isExport: boolean): Token|Ast {
2578
2573
  let gname: string = (word as AstStrValue).value
@@ -2630,6 +2625,10 @@ export class NakoParser extends NakoParserBase {
2630
2625
  return words
2631
2626
  }
2632
2627
 
2628
+ // ---------------------------------------------------------------------------
2629
+ // JSON/配列リテラル
2630
+ // ---------------------------------------------------------------------------
2631
+
2633
2632
  yJSONObjectValue(): Ast[] {
2634
2633
  // 戻り値の形式
2635
2634
  // Astblocks.blocks = [key1, value1, key2, value2, key3, value3 ...]
@@ -2804,6 +2803,10 @@ export class NakoParser extends NakoParserBase {
2804
2803
  return null
2805
2804
  }
2806
2805
 
2806
+ // ---------------------------------------------------------------------------
2807
+ // エラー監視
2808
+ // ---------------------------------------------------------------------------
2809
+
2807
2810
  /** エラー監視構文 */
2808
2811
  yTryExcept(): AstBlocks | null {
2809
2812
  const map = this.peekSourceMap()
@@ -2834,67 +2837,9 @@ export class NakoParser extends NakoParserBase {
2834
2837
  }
2835
2838
  }
2836
2839
 
2837
- /** 関数ごとにasyncFnが必要か確認する */
2838
- _checkAsyncFn(node: Ast): boolean {
2839
- if (!node) { return false }
2840
- // 関数定義があれば関数
2841
- if (node.type === 'def_func' || node.type === 'def_test' || node.type === 'func_obj') {
2842
- // 関数定義でasyncFnが指定されているならtrueを返す
2843
- const def: AstDefFunc = node as AstDefFunc
2844
- if (def.asyncFn) { return true } // 既にasyncFnが指定されている
2845
- // 関数定義の中身を調べてasyncFnであるならtrueに変更する
2846
- let isAsyncFn = false
2847
- for (const n of def.blocks) {
2848
- if (this._checkAsyncFn(n)) {
2849
- isAsyncFn = true
2850
- def.asyncFn = isAsyncFn
2851
- def.meta.asyncFn = isAsyncFn
2852
- this.isModifiedNodes = true
2853
- return true
2854
- }
2855
- }
2856
- }
2857
- // 関数呼び出しを調べて非同期処理が必要ならtrueを返す
2858
- if (node.type === 'func') {
2859
- // 関数呼び出し自体が非同期処理ならtrueを返す
2860
- const callNode: AstCallFunc = node as AstCallFunc
2861
- if (callNode.asyncFn) {
2862
- return true
2863
- }
2864
- // 続けて、以下の関数呼び出しの引数などに非同期処理があるかどうか調べる
2865
- // 関数の引数は、node.blocksに格納されている
2866
- if (callNode.blocks) {
2867
- for (const n of callNode.blocks) {
2868
- if (this._checkAsyncFn(n)) {
2869
- callNode.asyncFn = true
2870
- this.isModifiedNodes = true
2871
- return true
2872
- }
2873
- }
2874
- }
2875
- // さらに、関数のリンクを調べる
2876
- const func = this.funclist.get(callNode.name)
2877
- if (func && func.asyncFn) {
2878
- callNode.asyncFn = true
2879
- this.isModifiedNodes = true
2880
- return true
2881
- }
2882
- return false
2883
- }
2884
- // 連文 ... 現在、効率は悪いが非同期で実行することになっている
2885
- if (node.type === 'renbun') {
2886
- return true
2887
- }
2888
- // その他
2889
- if ((node as AstBlocks).blocks) {
2890
- for (const n of (node as AstBlocks).blocks) {
2891
- if (this._checkAsyncFn(n)) {
2892
- return true
2893
- }
2894
- }
2895
- }
2896
- return false
2897
- }
2840
+ // ---------------------------------------------------------------------------
2841
+ // TokenからAstへの変換
2842
+ // ---------------------------------------------------------------------------
2898
2843
 
2899
2844
  /** TokenをそのままNodeに変換するメソッド(ただし簡単なものだけ対応)
2900
2845
  * @returns {Ast[]}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 構文解析後の AST を走査して、非同期処理(asyncFn)の要否を判定するモジュール
3
+ *
4
+ * NakoParser から分離した (#2364)。
5
+ * 構文解析が完了した後に AST 全体を走査する後処理パスであり、
6
+ * トークンのカーソルにも計算用スタックにも一切触れない。
7
+ * (実体は nako_parser3.mts の NakoParser._checkAsyncFn だった)
8
+ *
9
+ * 非同期の情報は「非同期関数を呼ぶ関数もまた非同期」という形で伝播していくため、
10
+ * 1 回の走査では行き渡らないことがある。呼び出し側は変化が無くなるまで
11
+ * `checkAsyncFn()` を繰り返し呼ぶこと。
12
+ */
13
+ import { Ast, AstBlocks, AstDefFunc, AstCallFunc, AstLet } from './nako_ast.mjs'
14
+ import { FuncList } from './nako_types.mjs'
15
+
16
+ /** 走査中に AST を書き換えたかどうかを持ち回るための入れ物 */
17
+ interface CheckContext {
18
+ modified: boolean
19
+ /**
20
+ * 無名関数を保持している変数名 → その無名関数が非同期かどうか
21
+ *
22
+ * `F=●()...ここまで` のように変数へ代入された無名関数は funclist に載らないため、
23
+ * 代入を覚えておかないと `F()` の呼び出しに asyncFn を付けられない。
24
+ * 変数のシャドーイングを考慮して、関数定義ごとにスコープを積む。
25
+ *
26
+ * 同期な無名関数の代入も記録する。記録しないと、内側のスコープで同名の変数へ
27
+ * 同期な無名関数を代入しても外側の非同期な代入が見えてしまう。
28
+ */
29
+ funcObjVarScopes: Map<string, boolean>[]
30
+ }
31
+
32
+ /**
33
+ * AST を走査して関数ごとに asyncFn が必要かどうかを確認し、必要なら書き換える。
34
+ *
35
+ * @param node 走査対象の AST
36
+ * @param funclist 関数一覧(呼び出し先が非同期かどうかの判定に使う)
37
+ * @returns AST を書き換えたら true。呼び出し側は false になるまで繰り返すこと
38
+ */
39
+ export function checkAsyncFn (node: Ast, funclist: FuncList): boolean {
40
+ const ctx: CheckContext = { modified: false, funcObjVarScopes: [new Map()] }
41
+ checkNode(node, funclist, ctx)
42
+ return ctx.modified
43
+ }
44
+
45
+ /**
46
+ * 変数名が非同期の無名関数を指しているかどうかを、内側のスコープから順に調べる。
47
+ * 見つかった時点で打ち切るので、内側の代入が外側の代入をシャドーイングする。
48
+ */
49
+ function isAsyncVar (ctx: CheckContext, name: string): boolean {
50
+ for (let i = ctx.funcObjVarScopes.length - 1; i >= 0; i--) {
51
+ const asyncFn = ctx.funcObjVarScopes[i].get(name)
52
+ if (asyncFn !== undefined) { return asyncFn }
53
+ }
54
+ return false
55
+ }
56
+
57
+ /**
58
+ * 変数へ無名関数を代入していたら、現在のスコープに覚えておく
59
+ * (`let` と `def_local_var` はどちらも blocks[0] が代入する値)
60
+ *
61
+ * 無名関数以外の代入は記録しない。`F=Gの参照` のような代入まで同期扱いにすると、
62
+ * 逆に await の付け漏れという、より悪い誤りになるため。
63
+ */
64
+ function checkFuncObjVarAssign (node: Ast, ctx: CheckContext): void {
65
+ const letNode = node as AstLet
66
+ if (!letNode.name) { return }
67
+ const value = (letNode.blocks || [])[0]
68
+ if (!value || value.type !== 'func_obj') { return }
69
+ const scope = ctx.funcObjVarScopes[ctx.funcObjVarScopes.length - 1]
70
+ scope.set(letNode.name, !!(value as AstDefFunc).asyncFn)
71
+ }
72
+
73
+ /**
74
+ * ノードが非同期処理を含むかどうかを返す(必要に応じて AST を書き換える)
75
+ * @returns 非同期処理を含むなら true
76
+ */
77
+ function checkNode (node: Ast, funclist: FuncList, ctx: CheckContext): boolean {
78
+ if (!node) { return false }
79
+ // 関数定義があれば関数
80
+ if (node.type === 'def_func' || node.type === 'def_test' || node.type === 'func_obj') {
81
+ const def: AstDefFunc = node as AstDefFunc
82
+ // 既に非同期と分かっていても中身の走査は省略しない。
83
+ // 途中で打ち切ると、それより後ろにある呼び出しに asyncFn が付かなくなる
84
+ ctx.funcObjVarScopes.push(new Map())
85
+ let isAsyncFn = false
86
+ for (const n of def.blocks) {
87
+ if (checkNode(n, funclist, ctx)) { isAsyncFn = true }
88
+ }
89
+ ctx.funcObjVarScopes.pop()
90
+ // 関数定義の中身が非同期であるならasyncFnをtrueに変更する
91
+ if (isAsyncFn && !def.asyncFn) {
92
+ def.asyncFn = true
93
+ def.meta.asyncFn = true
94
+ ctx.modified = true
95
+ }
96
+ if (!def.asyncFn) { return false }
97
+ // 後から判明した非同期情報を、呼び出し側の判定に使う関数一覧にも反映する
98
+ const func = funclist.get(def.name)
99
+ if (func && !func.asyncFn) {
100
+ func.asyncFn = true
101
+ ctx.modified = true
102
+ }
103
+ return true
104
+ }
105
+ // 変数へ代入された無名関数を覚えておく(呼び出し側の判定に使う)
106
+ if (node.type === 'let' || node.type === 'def_local_var') {
107
+ let containsAsync = false
108
+ for (const n of (node as AstBlocks).blocks) {
109
+ if (checkNode(n, funclist, ctx)) { containsAsync = true }
110
+ }
111
+ checkFuncObjVarAssign(node, ctx)
112
+ return containsAsync
113
+ }
114
+ // 関数呼び出しを調べて非同期処理が必要ならtrueを返す
115
+ if (node.type === 'func') {
116
+ // 関数呼び出し自体が非同期処理ならtrueを返す
117
+ const callNode: AstCallFunc = node as AstCallFunc
118
+ if (callNode.asyncFn) {
119
+ return true
120
+ }
121
+ // 続けて、以下の関数呼び出しの引数などに非同期処理があるかどうか調べる
122
+ // 関数の引数は、node.blocksに格納されている
123
+ // (2つ目以降の引数にも asyncFn が必要なので、最初の1つで打ち切らない)
124
+ if (callNode.blocks) {
125
+ let hasAsyncArg = false
126
+ for (const n of callNode.blocks) {
127
+ if (checkNode(n, funclist, ctx)) { hasAsyncArg = true }
128
+ }
129
+ if (hasAsyncArg) {
130
+ callNode.asyncFn = true
131
+ ctx.modified = true
132
+ return true
133
+ }
134
+ }
135
+ // さらに、関数のリンクを調べる
136
+ const func = funclist.get(callNode.name)
137
+ if (func && func.asyncFn) {
138
+ callNode.asyncFn = true
139
+ ctx.modified = true
140
+ return true
141
+ }
142
+ // 非同期の無名関数を保持している変数の呼び出しかどうかを調べる
143
+ if (isAsyncVar(ctx, callNode.name)) {
144
+ callNode.asyncFn = true
145
+ ctx.modified = true
146
+ return true
147
+ }
148
+ return false
149
+ }
150
+ // 連文 ... 現在、効率は悪いが非同期で実行することになっている
151
+ if (node.type === 'renbun') {
152
+ return true
153
+ }
154
+ // その他
155
+ if ((node as AstBlocks).blocks) {
156
+ let containsAsync = false
157
+ for (const n of (node as AstBlocks).blocks) {
158
+ if (checkNode(n, funclist, ctx)) {
159
+ containsAsync = true
160
+ }
161
+ }
162
+ return containsAsync
163
+ }
164
+ return false
165
+ }