nadesiko3 3.8.7 → 3.8.9

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 (79) hide show
  1. package/README.md +27 -70
  2. package/batch/command.txt +26 -805
  3. package/batch/doctest.mjs +3 -0
  4. package/batch/doctest.nako3 +514 -0
  5. package/batch/pickup_command.nako3 +0 -4
  6. package/core/package-lock.json +17 -20
  7. package/core/package.json +3 -3
  8. package/core/src/nako_core_version.mjs +2 -2
  9. package/core/src/nako_core_version.mts +2 -2
  10. package/core/src/nako_csv.mjs +9 -0
  11. package/core/src/nako_csv.mts +12 -0
  12. package/core/src/nako_gen.mjs +7 -3
  13. package/core/src/nako_gen.mts +7 -3
  14. package/core/src/nako_global.mjs +2 -0
  15. package/core/src/nako_global.mts +4 -0
  16. package/core/src/nako_parser3.mjs +94 -1
  17. package/core/src/nako_parser3.mts +69 -1
  18. package/core/src/nako_read_index.mjs +43 -0
  19. package/core/src/nako_read_index.mts +43 -0
  20. package/core/src/plugin_csv.mjs +14 -10
  21. package/core/src/plugin_csv.mts +14 -10
  22. package/core/src/plugin_toml.mjs +3 -3
  23. package/core/src/plugin_toml.mts +3 -3
  24. package/core/test/array_slice_test.mjs +39 -0
  25. package/core/test/array_test.mjs +54 -0
  26. package/core/test/fixtures/compat/SPEC.md +3 -3
  27. package/core/test/fixtures/compat/cases/04_string.json +1 -4
  28. package/core/test/fixtures/compat/expected/04_string.json +1 -1
  29. package/core/test/plugin_csv_test.mjs +20 -0
  30. package/core/test/plugin_toml_test.mjs +25 -0
  31. package/core/test/string_index_test.mjs +131 -0
  32. package/demo/browsers.html +11 -11
  33. package/doc/browsers.md +11 -11
  34. package/doc/command_list.json +82 -7236
  35. package/doc/doctest.md +87 -3
  36. package/doc/syntax-nako3.md +9 -0
  37. package/package.json +6 -6
  38. package/release/_hash.txt +36 -36
  39. package/release/_script-tags.txt +16 -16
  40. package/release/command.json +1 -1
  41. package/release/command.json.js +1 -1
  42. package/release/command_cnako3.json +1 -1
  43. package/release/command_list.json +1 -1
  44. package/release/edit_main.js +8 -8
  45. package/release/edit_main.js.map +3 -3
  46. package/release/editor.js +8 -8
  47. package/release/plugin_caniuse.js +1 -1
  48. package/release/plugin_caniuse.js.map +2 -2
  49. package/release/plugin_markup.js +40 -38
  50. package/release/plugin_markup.js.map +3 -3
  51. package/release/version.js +1 -1
  52. package/release/version_main.js +1 -1
  53. package/release/version_main.js.map +1 -1
  54. package/release/wnako3.js +192 -226
  55. package/release/wnako3.js.map +4 -4
  56. package/release/wnako3webworker.js +180 -243
  57. package/release/wnako3webworker.js.map +4 -4
  58. package/src/browsers.mjs +1 -1
  59. package/src/browsers.txt +31 -31
  60. package/src/nako_version.mjs +2 -2
  61. package/src/nako_version.mts +2 -2
  62. package/src/plugin_browser.mjs +3 -1
  63. package/src/plugin_browser.mts +3 -1
  64. package/src/plugin_browser_dialog.mjs +115 -17
  65. package/src/plugin_browser_dialog.mts +103 -17
  66. package/src/plugin_browser_dialog_dom.mjs +247 -0
  67. package/src/plugin_browser_dialog_dom.mts +258 -0
  68. package/test/common/plugin_browser_ut_audio_test.mjs +6 -7
  69. package/test/common/plugin_browser_ut_dialog_test.mjs +66 -71
  70. package/test/common/wnako3mod_test.mjs +6 -0
  71. package/test/node/build_command_test.mjs +39 -0
  72. package/test/node/doctest_nako3_test.mjs +112 -0
  73. package/test/node/fixtures/doctest_nako3/ng.txt +16 -0
  74. package/test/node/fixtures/doctest_nako3/ok.txt +18 -0
  75. package/test/node/node_test.mjs +10 -0
  76. package/test/node/node_version_support_test.mjs +15 -0
  77. package/test/node/package_json_test.mjs +14 -0
  78. package/test/node/plugin_browser_dialog_test.mjs +379 -0
  79. package/test/node/plugin_browser_ut_location_test.mjs +3 -5
@@ -11,9 +11,9 @@ export interface NakoCoreVersion {
11
11
  }
12
12
  // 実際のバージョン定義 (自動生成されるので以下を編集しない)
13
13
  const coreVersion: NakoCoreVersion = {
14
- version: '3.8.7',
14
+ version: '3.8.9',
15
15
  major: 3,
16
16
  minor: 8,
17
- patch: 7
17
+ patch: 9
18
18
  }
19
19
  export default coreVersion
@@ -3,10 +3,19 @@ export const options = {
3
3
  eol: '\r\n',
4
4
  auto_convert_number: true
5
5
  };
6
+ // delimiter が明示的に設定されたかどうかを追跡するフラグ
7
+ let delimiterExplicitlySet = false;
6
8
  export function resetEnv() {
7
9
  options.delimiter = ',';
8
10
  options.eol = '\r\n';
9
11
  options.auto_convert_number = true;
12
+ delimiterExplicitlySet = false;
13
+ }
14
+ export function isDelimiterExplicitlySet() {
15
+ return delimiterExplicitlySet;
16
+ }
17
+ export function setDelimiterExplicitlySet(value) {
18
+ delimiterExplicitlySet = value;
10
19
  }
11
20
  /** 文字列が数値かどうか判定する関数
12
21
  * @param str 判定する文字列
@@ -11,10 +11,22 @@ export const options: CSVOptions = {
11
11
  auto_convert_number: true
12
12
  }
13
13
 
14
+ // delimiter が明示的に設定されたかどうかを追跡するフラグ
15
+ let delimiterExplicitlySet = false
16
+
14
17
  export function resetEnv(): void {
15
18
  options.delimiter = ','
16
19
  options.eol = '\r\n'
17
20
  options.auto_convert_number = true
21
+ delimiterExplicitlySet = false
22
+ }
23
+
24
+ export function isDelimiterExplicitlySet(): boolean {
25
+ return delimiterExplicitlySet
26
+ }
27
+
28
+ export function setDelimiterExplicitlySet(value: boolean): void {
29
+ delimiterExplicitlySet = value
18
30
  }
19
31
 
20
32
  /** 文字列が数値かどうか判定する関数
@@ -5,6 +5,7 @@
5
5
  import { NakoSyntaxError } from './nako_errors.mjs';
6
6
  import { incValue } from './nako_inc_value.mjs';
7
7
  import { dnclEnsureArray } from './nako_dncl_ensure_array.mjs';
8
+ import { createReadIndex } from './nako_read_index.mjs';
8
9
  // なでしこで定義した関数の開始コードと終了コード
9
10
  const topOfFunction = '(function(){\n';
10
11
  const endOfFunction = '})';
@@ -1097,7 +1098,7 @@ export class NakoGen {
1097
1098
  code = `__self.__dncl_ensure_array(${code},${idx})`;
1098
1099
  }
1099
1100
  else {
1100
- code += '[' + idx + ']';
1101
+ code = `__self.__readIndex(${code},${idx})`;
1101
1102
  }
1102
1103
  }
1103
1104
  return code;
@@ -1179,7 +1180,7 @@ export class NakoGen {
1179
1180
  if (op === '@' || op === '[') {
1180
1181
  for (let i = 0; i < list.length; i++) {
1181
1182
  const idx = this._convGen(list[i], true);
1182
- code += '[' + idx + ']';
1183
+ code = `__self.__readIndex(${code},${idx})`;
1183
1184
  }
1184
1185
  return code;
1185
1186
  }
@@ -2330,6 +2331,8 @@ self.version = '__version__'
2330
2331
  self.__incValue = __incValueCode__
2331
2332
  // DNCLモードの多次元配列の中間要素の自動初期化。コア実行環境(NakoGlobal)と同じ実装を埋め込む (#1140)
2332
2333
  self.__dncl_ensure_array = __dnclEnsureArrayCode__
2334
+ // 文字列・配列・辞書の添字による読み取り (#2590)
2335
+ self.__readIndex = (__readIndexCode__)()
2333
2336
  self.logger = {
2334
2337
  error: (message) => { console.error(message) },
2335
2338
  warn: (message) => { console.warn(message) },
@@ -2538,7 +2541,8 @@ ${runtimeResult}
2538
2541
  'codeJS': js,
2539
2542
  jsInit,
2540
2543
  incValueCode: String(incValue),
2541
- dnclEnsureArrayCode: String(dnclEnsureArray)
2544
+ dnclEnsureArrayCode: String(dnclEnsureArray),
2545
+ readIndexCode: String(createReadIndex)
2542
2546
  }),
2543
2547
  // コード生成に使ったNakoGenのインスタンス
2544
2548
  gen
@@ -11,6 +11,7 @@ import { Ast, AstEol, AstStrValue, AstBlocks, AstOperator, AstConst, AstInc, Ast
11
11
  import { NakoCompiler } from './nako3.mjs'
12
12
  import { incValue } from './nako_inc_value.mjs'
13
13
  import { dnclEnsureArray } from './nako_dncl_ensure_array.mjs'
14
+ import { createReadIndex } from './nako_read_index.mjs'
14
15
 
15
16
  // なでしこで定義した関数の開始コードと終了コード
16
17
  const topOfFunction = '(function(){\n'
@@ -1195,7 +1196,7 @@ export class NakoGen {
1195
1196
  // 配列・添字の式を一度だけ評価するようランタイムヘルパーに委譲する)
1196
1197
  code = `__self.__dncl_ensure_array(${code},${idx})`
1197
1198
  } else {
1198
- code += '[' + idx + ']'
1199
+ code = `__self.__readIndex(${code},${idx})`
1199
1200
  }
1200
1201
  }
1201
1202
  return code
@@ -1266,7 +1267,7 @@ export class NakoGen {
1266
1267
  if (op === '@' || op === '[') {
1267
1268
  for (let i = 0; i < list.length; i++) {
1268
1269
  const idx = this._convGen(list[i], true)
1269
- code += '[' + idx + ']'
1270
+ code = `__self.__readIndex(${code},${idx})`
1270
1271
  }
1271
1272
  return code
1272
1273
  }
@@ -2423,6 +2424,8 @@ self.version = '__version__'
2423
2424
  self.__incValue = __incValueCode__
2424
2425
  // DNCLモードの多次元配列の中間要素の自動初期化。コア実行環境(NakoGlobal)と同じ実装を埋め込む (#1140)
2425
2426
  self.__dncl_ensure_array = __dnclEnsureArrayCode__
2427
+ // 文字列・配列・辞書の添字による読み取り (#2590)
2428
+ self.__readIndex = (__readIndexCode__)()
2426
2429
  self.logger = {
2427
2430
  error: (message) => { console.error(message) },
2428
2431
  warn: (message) => { console.warn(message) },
@@ -2634,7 +2637,8 @@ ${runtimeResult}
2634
2637
  'codeJS': js,
2635
2638
  jsInit,
2636
2639
  incValueCode: String(incValue),
2637
- dnclEnsureArrayCode: String(dnclEnsureArray)
2640
+ dnclEnsureArrayCode: String(dnclEnsureArray),
2641
+ readIndexCode: String(createReadIndex)
2638
2642
  }),
2639
2643
  // コード生成に使ったNakoGenのインスタンス
2640
2644
  gen
@@ -1,6 +1,7 @@
1
1
  import { NakoColors } from './nako_colors.mjs';
2
2
  import { incValue } from './nako_inc_value.mjs';
3
3
  import { dnclEnsureArray } from './nako_dncl_ensure_array.mjs';
4
+ import { createReadIndex } from './nako_read_index.mjs';
4
5
  /**
5
6
  * コンパイルされたなでしこのプログラムで、グローバル空間のthisが指すオブジェクト
6
7
  */
@@ -45,6 +46,7 @@ export class NakoGlobal {
45
46
  // DNCLモードの多次元配列の中間要素の自動初期化。
46
47
  // 単体JavaScript(standalone)にも同じ実装を埋め込む (#1140)
47
48
  this.__dncl_ensure_array = dnclEnsureArray;
49
+ this.__readIndex = createReadIndex();
48
50
  }
49
51
  clearLog() {
50
52
  this.__varslist[0].set('表示ログ', '');
@@ -6,6 +6,7 @@ import { CompilerOptions, NakoVars } from './nako_types.mjs'
6
6
  import { NakoValue } from './plugin_api.mjs'
7
7
  import { incValue } from './nako_inc_value.mjs'
8
8
  import { dnclEnsureArray } from './nako_dncl_ensure_array.mjs'
9
+ import { createReadIndex } from './nako_read_index.mjs'
9
10
 
10
11
  /**
11
12
  * コンパイルされたなでしこのプログラムで、グローバル空間のthisが指すオブジェクト
@@ -36,6 +37,8 @@ export class NakoGlobal {
36
37
  __incValue: (a: NakoValue, b: NakoValue, isDec: boolean) => number | bigint
37
38
  // DNCLモードの多次元配列の中間要素の自動初期化 (#1140)
38
39
  __dncl_ensure_array: (base: any, idx: any) => any
40
+ // 文字列・配列・辞書の添字による読み取り (#2590)
41
+ __readIndex: (base: any, index: any) => any
39
42
  /**
40
43
  * @param compiler
41
44
  * @param gen
@@ -80,6 +83,7 @@ export class NakoGlobal {
80
83
  // DNCLモードの多次元配列の中間要素の自動初期化。
81
84
  // 単体JavaScript(standalone)にも同じ実装を埋め込む (#1140)
82
85
  this.__dncl_ensure_array = dnclEnsureArray
86
+ this.__readIndex = createReadIndex()
83
87
  }
84
88
 
85
89
  clearLog() {
@@ -1415,7 +1415,8 @@ export class NakoParser extends NakoParserBase {
1415
1415
  return null;
1416
1416
  }
1417
1417
  const value = this.popStack(['を']) || { type: 'word', value: 'それ', josi: 'を', ...map };
1418
- const word = this.popStack(['へ', 'に']);
1418
+ const dest = this.popStack(['へ', 'に']);
1419
+ const word = dest ? (this.normalizeArrayAssignmentTarget(dest) || dest) : null;
1419
1420
  if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== 'ref_array')) {
1420
1421
  throw NakoSyntaxError.fromNode('代入文で代入先の変数が見当たりません。『(変数名)に(値)を代入』のように使います。', dainyu);
1421
1422
  }
@@ -1784,6 +1785,32 @@ export class NakoParser extends NakoParserBase {
1784
1785
  /** @returns {Ast | null} */
1785
1786
  yLet() {
1786
1787
  const map = this.peekSourceMap();
1788
+ // 括弧付きの配列要素への代入。式の中の比較は従来どおり yCalc で読む。(#2583)
1789
+ if (this.check('(') && this.hasParenthesizedAssignment()) {
1790
+ const value = this.yValue();
1791
+ const target = value ? this.normalizeArrayAssignmentTarget(value) : null;
1792
+ if (target?.type === 'ref_array' && this.check('eq')) {
1793
+ this.get(); // skip '=' / 'は'
1794
+ const astValue = this.yCalc();
1795
+ if (!astValue) {
1796
+ throw NakoSyntaxError.fromNode('配列への代入文で値がありません。', map);
1797
+ }
1798
+ const resolved = this.getAssignmentVarName(target);
1799
+ if (this.check('comma')) {
1800
+ this.get();
1801
+ }
1802
+ return {
1803
+ type: 'let_array',
1804
+ name: resolved.name.value,
1805
+ blocks: [astValue, ...(resolved.index || [])],
1806
+ josi: '',
1807
+ checkInit: target.checkInit,
1808
+ ...map,
1809
+ end: this.peekSourceMap()
1810
+ };
1811
+ }
1812
+ throw NakoSyntaxError.fromNode('括弧付きの代入先は、変数を起点とする配列要素で指定してください。', map);
1813
+ }
1787
1814
  // 通常の変数
1788
1815
  if (this.check2(['word', 'eq'])) {
1789
1816
  const word = this.peek();
@@ -2876,6 +2903,72 @@ export class NakoParser extends NakoParserBase {
2876
2903
  }
2877
2904
  return word;
2878
2905
  }
2906
+ /** 括弧付き代入の候補をトークンだけで確認する。(#2583)
2907
+ * yValue の先読みは変数名や関数の使用記録も更新するため、解析後の巻き戻しはしない。
2908
+ * 括弧内の比較は無視し、外側の助詞・演算子・文末で探索を終了する。
2909
+ */
2910
+ hasParenthesizedAssignment() {
2911
+ const closings = [];
2912
+ for (let i = this.index; i < this.tokens.length; i++) {
2913
+ const token = this.tokens[i];
2914
+ if (closings.length === 0) {
2915
+ if (token.type === 'eq') {
2916
+ return true;
2917
+ }
2918
+ // @直後の単項演算子は添字の一部。値の後ろの二項演算子とは区別する。
2919
+ const unaryIndex = ['-', 'not'].includes(token.type) && ['@', 'not'].includes(this.tokens[i - 1]?.type);
2920
+ if (!unaryIndex && !['(', '[', '{', '@', 'word', 'func', 'func_pointer', 'number', 'bigint', 'string'].includes(token.type)) {
2921
+ return false;
2922
+ }
2923
+ }
2924
+ if (token.type === '(') {
2925
+ closings.push(')');
2926
+ }
2927
+ else if (token.type === '[') {
2928
+ closings.push(']');
2929
+ }
2930
+ else if (token.type === '{') {
2931
+ closings.push('}');
2932
+ }
2933
+ else if ([')', ']', '}'].includes(token.type)) {
2934
+ if (closings.pop() !== token.type) {
2935
+ return false;
2936
+ }
2937
+ }
2938
+ if (closings.length === 0 && token.josi !== '') {
2939
+ return false;
2940
+ }
2941
+ }
2942
+ return false;
2943
+ }
2944
+ /** 括弧付きの配列参照を、変数を起点とする代入先へ変換する。(#2583)
2945
+ * 添字は内側から順に連結し、参照用のASTは書き換えない。
2946
+ * 関数の戻り値やリテラルなど、変数を起点としない参照は対象外。
2947
+ */
2948
+ normalizeArrayAssignmentTarget(node) {
2949
+ if (node.type === 'word') {
2950
+ return node;
2951
+ }
2952
+ // ref_array の name は文字列の場合もあるが、ここでは変数トークンを持つ参照だけを扱う。
2953
+ if (node.type === 'ref_array' && typeof node.name !== 'string' && node.name?.type === 'word') {
2954
+ return { ...node, index: [...(node.index || [])] };
2955
+ }
2956
+ if (node.type !== 'ref_array_value' || (node.name !== '@' && node.name !== '[')) {
2957
+ return null;
2958
+ }
2959
+ const [base, ...indexes] = node.index || [];
2960
+ const target = base ? this.normalizeArrayAssignmentTarget(base) : null;
2961
+ if (!target) {
2962
+ return null;
2963
+ }
2964
+ return {
2965
+ ...node,
2966
+ type: 'ref_array',
2967
+ name: target.type === 'word' ? target : target.name,
2968
+ index: [...(target.index || []), ...indexes],
2969
+ checkInit: this.flagCheckArrayInit
2970
+ };
2971
+ }
2879
2972
  /** 代入・増減対象の変数名を、元の無修飾名を考慮して解決する */
2880
2973
  getAssignmentVarName(word) {
2881
2974
  if (word.type === 'word') {
@@ -1249,7 +1249,8 @@ export class NakoParser extends NakoParserBase {
1249
1249
  const dainyu = this.get() // 代入
1250
1250
  if (dainyu === null) { return null }
1251
1251
  const value = this.popStack(['を']) || { type: 'word', value: 'それ', josi: 'を', ...map } as AstStrValue
1252
- const word: Ast|null = this.popStack(['へ', 'に'])
1252
+ const dest = this.popStack(['へ', 'に'])
1253
+ const word = dest ? (this.normalizeArrayAssignmentTarget(dest) || dest) : null
1253
1254
  if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== 'ref_array')) {
1254
1255
  throw NakoSyntaxError.fromNode('代入文で代入先の変数が見当たりません。『(変数名)に(値)を代入』のように使います。', dainyu)
1255
1256
  }
@@ -1580,6 +1581,28 @@ export class NakoParser extends NakoParserBase {
1580
1581
  /** @returns {Ast | null} */
1581
1582
  yLet(): AstBlocks | null {
1582
1583
  const map = this.peekSourceMap()
1584
+ // 括弧付きの配列要素への代入。式の中の比較は従来どおり yCalc で読む。(#2583)
1585
+ if (this.check('(') && this.hasParenthesizedAssignment()) {
1586
+ const value = this.yValue()
1587
+ const target = value ? this.normalizeArrayAssignmentTarget(value) : null
1588
+ if (target?.type === 'ref_array' && this.check('eq')) {
1589
+ this.get() // skip '=' / 'は'
1590
+ const astValue = this.yCalc()
1591
+ if (!astValue) { throw NakoSyntaxError.fromNode('配列への代入文で値がありません。', map) }
1592
+ const resolved = this.getAssignmentVarName(target)
1593
+ if (this.check('comma')) { this.get() }
1594
+ return {
1595
+ type: 'let_array',
1596
+ name: (resolved.name as AstStrValue).value,
1597
+ blocks: [astValue, ...(resolved.index || [])],
1598
+ josi: '',
1599
+ checkInit: target.checkInit,
1600
+ ...map,
1601
+ end: this.peekSourceMap()
1602
+ } as AstLetArray
1603
+ }
1604
+ throw NakoSyntaxError.fromNode('括弧付きの代入先は、変数を起点とする配列要素で指定してください。', map)
1605
+ }
1583
1606
  // 通常の変数
1584
1607
  if (this.check2(['word', 'eq'])) {
1585
1608
  const word = this.peek()
@@ -2577,6 +2600,51 @@ export class NakoParser extends NakoParserBase {
2577
2600
  return word
2578
2601
  }
2579
2602
 
2603
+ /** 括弧付き代入の候補をトークンだけで確認する。(#2583)
2604
+ * yValue の先読みは変数名や関数の使用記録も更新するため、解析後の巻き戻しはしない。
2605
+ * 括弧内の比較は無視し、外側の助詞・演算子・文末で探索を終了する。
2606
+ */
2607
+ private hasParenthesizedAssignment(): boolean {
2608
+ const closings: string[] = []
2609
+ for (let i = this.index; i < this.tokens.length; i++) {
2610
+ const token = this.tokens[i]
2611
+ if (closings.length === 0) {
2612
+ if (token.type === 'eq') { return true }
2613
+ // @直後の単項演算子は添字の一部。値の後ろの二項演算子とは区別する。
2614
+ const unaryIndex = ['-', 'not'].includes(token.type) && ['@', 'not'].includes(this.tokens[i - 1]?.type)
2615
+ if (!unaryIndex && !['(', '[', '{', '@', 'word', 'func', 'func_pointer', 'number', 'bigint', 'string'].includes(token.type)) { return false }
2616
+ }
2617
+ if (token.type === '(') { closings.push(')') } else if (token.type === '[') { closings.push(']') } else if (token.type === '{') { closings.push('}') } else if ([')', ']', '}'].includes(token.type)) {
2618
+ if (closings.pop() !== token.type) { return false }
2619
+ }
2620
+ if (closings.length === 0 && token.josi !== '') { return false }
2621
+ }
2622
+ return false
2623
+ }
2624
+
2625
+ /** 括弧付きの配列参照を、変数を起点とする代入先へ変換する。(#2583)
2626
+ * 添字は内側から順に連結し、参照用のASTは書き換えない。
2627
+ * 関数の戻り値やリテラルなど、変数を起点としない参照は対象外。
2628
+ */
2629
+ private normalizeArrayAssignmentTarget(node: Ast): Ast | null {
2630
+ if (node.type === 'word') { return node }
2631
+ // ref_array の name は文字列の場合もあるが、ここでは変数トークンを持つ参照だけを扱う。
2632
+ if (node.type === 'ref_array' && typeof node.name !== 'string' && node.name?.type === 'word') {
2633
+ return { ...node, index: [...(node.index || [])] }
2634
+ }
2635
+ if (node.type !== 'ref_array_value' || (node.name !== '@' && node.name !== '[')) { return null }
2636
+ const [base, ...indexes] = node.index || []
2637
+ const target = base ? this.normalizeArrayAssignmentTarget(base) : null
2638
+ if (!target) { return null }
2639
+ return {
2640
+ ...node,
2641
+ type: 'ref_array',
2642
+ name: target.type === 'word' ? target : target.name,
2643
+ index: [...(target.index || []), ...indexes],
2644
+ checkInit: this.flagCheckArrayInit
2645
+ }
2646
+ }
2647
+
2580
2648
  /** 代入・増減対象の変数名を、元の無修飾名を考慮して解決する */
2581
2649
  getAssignmentVarName(word: Ast): Ast {
2582
2650
  if (word.type === 'word') {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 添字による読み取り (#2590, #2599)。文字列はUnicodeコードポイント単位で扱い、
3
+ * 文字列・配列の範囲の末尾は含めない。通常の配列・辞書・プロパティはJavaScriptの参照を使う。
4
+ * 単体JavaScriptにも埋め込むため、外部の関数には依存しない。
5
+ */
6
+ export function createReadIndex() {
7
+ // 実行環境ごとに最近使った文字列を最大4件保持する。複数の文字列を
8
+ // 交互に読む場合も全文の走査・配列化を繰り返さず、無制限に蓄積しない。
9
+ const cache = new Map();
10
+ return function readIndex(base, index) {
11
+ const isRange = index !== null && typeof index === 'object' &&
12
+ typeof index['先頭'] === 'number' && typeof index['末尾'] === 'number';
13
+ if (Array.isArray(base) && isRange) {
14
+ return base.slice(index['先頭'], index['末尾']);
15
+ }
16
+ if (typeof base === 'string') {
17
+ // 数字の文字列による添字も受け付ける。lengthなどのプロパティは通常の参照に戻す。
18
+ const numberIndex = typeof index === 'string' && /^-?(0|[1-9]\d*)$/.test(index)
19
+ ? Number(index) : index;
20
+ if (isRange || (typeof numberIndex === 'number' && Number.isInteger(numberIndex))) {
21
+ let cachedChars = cache.get(base);
22
+ if (cachedChars === undefined) {
23
+ // サロゲートを含まなければ文字列を直接読む。判定も同じ文字列では一度だけ。
24
+ cachedChars = /[\uD800-\uDFFF]/.test(base) ? Array.from(base) : null;
25
+ if (cache.size >= 4) {
26
+ cache.delete(cache.keys().next().value);
27
+ }
28
+ }
29
+ // ヒットした文字列も末尾へ移し、長く使われていないものから破棄する。
30
+ cache.delete(base);
31
+ cache.set(base, cachedChars);
32
+ const chars = cachedChars ?? base;
33
+ if (isRange) {
34
+ return cachedChars === null
35
+ ? base.slice(index['先頭'], index['末尾'])
36
+ : cachedChars.slice(index['先頭'], index['末尾']).join('');
37
+ }
38
+ return chars[numberIndex < 0 ? chars.length + numberIndex : numberIndex];
39
+ }
40
+ }
41
+ return base[index];
42
+ };
43
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * 添字による読み取り (#2590, #2599)。文字列はUnicodeコードポイント単位で扱い、
3
+ * 文字列・配列の範囲の末尾は含めない。通常の配列・辞書・プロパティはJavaScriptの参照を使う。
4
+ * 単体JavaScriptにも埋め込むため、外部の関数には依存しない。
5
+ */
6
+ export function createReadIndex (): (base: any, index: any) => any {
7
+ // 実行環境ごとに最近使った文字列を最大4件保持する。複数の文字列を
8
+ // 交互に読む場合も全文の走査・配列化を繰り返さず、無制限に蓄積しない。
9
+ const cache = new Map<string, string[] | null>()
10
+ return function readIndex (base: any, index: any): any {
11
+ const isRange = index !== null && typeof index === 'object' &&
12
+ typeof index['先頭'] === 'number' && typeof index['末尾'] === 'number'
13
+ if (Array.isArray(base) && isRange) {
14
+ return base.slice(index['先頭'], index['末尾'])
15
+ }
16
+ if (typeof base === 'string') {
17
+ // 数字の文字列による添字も受け付ける。lengthなどのプロパティは通常の参照に戻す。
18
+ const numberIndex = typeof index === 'string' && /^-?(0|[1-9]\d*)$/.test(index)
19
+ ? Number(index) : index
20
+ if (isRange || (typeof numberIndex === 'number' && Number.isInteger(numberIndex))) {
21
+ let cachedChars = cache.get(base)
22
+ if (cachedChars === undefined) {
23
+ // サロゲートを含まなければ文字列を直接読む。判定も同じ文字列では一度だけ。
24
+ cachedChars = /[\uD800-\uDFFF]/.test(base) ? Array.from(base) : null
25
+ if (cache.size >= 4) {
26
+ cache.delete(cache.keys().next().value!)
27
+ }
28
+ }
29
+ // ヒットした文字列も末尾へ移し、長く使われていないものから破棄する。
30
+ cache.delete(base)
31
+ cache.set(base, cachedChars)
32
+ const chars = cachedChars ?? base
33
+ if (isRange) {
34
+ return cachedChars === null
35
+ ? base.slice(index['先頭'], index['末尾'])
36
+ : cachedChars.slice(index['先頭'], index['末尾']).join('')
37
+ }
38
+ return chars[numberIndex < 0 ? chars.length + numberIndex : numberIndex]
39
+ }
40
+ }
41
+ return base[index]
42
+ }
43
+ }
@@ -1,4 +1,4 @@
1
- import { options, parse, stringify } from './nako_csv.mjs';
1
+ import { options, parse, stringify, isDelimiterExplicitlySet, setDelimiterExplicitlySet } from './nako_csv.mjs';
2
2
  const PluginCSV = {
3
3
  'meta': {
4
4
  type: 'const',
@@ -24,7 +24,9 @@ const PluginCSV = {
24
24
  josi: [['を', 'の', 'で']],
25
25
  pure: true,
26
26
  fn: function (str) {
27
- options.delimiter = ',';
27
+ if (!isDelimiterExplicitlySet()) {
28
+ options.delimiter = ',';
29
+ }
28
30
  return parse(str);
29
31
  }
30
32
  },
@@ -33,8 +35,7 @@ const PluginCSV = {
33
35
  josi: [['を', 'の', 'で']],
34
36
  pure: true,
35
37
  fn: function (str) {
36
- options.delimiter = '\t';
37
- return parse(str);
38
+ return parse(str, '\t');
38
39
  }
39
40
  },
40
41
  '表CSV変換': {
@@ -42,7 +43,9 @@ const PluginCSV = {
42
43
  josi: [['を']],
43
44
  pure: true,
44
45
  fn: function (a) {
45
- options.delimiter = ',';
46
+ if (!isDelimiterExplicitlySet()) {
47
+ options.delimiter = ',';
48
+ }
46
49
  return stringify(a);
47
50
  }
48
51
  },
@@ -51,7 +54,9 @@ const PluginCSV = {
51
54
  josi: [['を']],
52
55
  pure: true,
53
56
  fn: function (a) {
54
- options.delimiter = ',';
57
+ if (!isDelimiterExplicitlySet()) {
58
+ options.delimiter = ',';
59
+ }
55
60
  return stringify(a);
56
61
  }
57
62
  },
@@ -60,8 +65,7 @@ const PluginCSV = {
60
65
  josi: [['を']],
61
66
  pure: true,
62
67
  fn: function (a) {
63
- options.delimiter = '\t';
64
- return stringify(a);
68
+ return stringify(a, '\t');
65
69
  }
66
70
  },
67
71
  'TSV変換': {
@@ -69,8 +73,7 @@ const PluginCSV = {
69
73
  josi: [['を']],
70
74
  pure: true,
71
75
  fn: function (a) {
72
- options.delimiter = '\t';
73
- return stringify(a);
76
+ return stringify(a, '\t');
74
77
  }
75
78
  },
76
79
  'CSVオプション設定': {
@@ -82,6 +85,7 @@ const PluginCSV = {
82
85
  const value = obj[key];
83
86
  if (key === 'delimiter' || key === '区切文字') {
84
87
  options.delimiter = value;
88
+ setDelimiterExplicitlySet(true);
85
89
  }
86
90
  else if (key === 'eol') {
87
91
  options.eol = value;
@@ -1,5 +1,5 @@
1
1
 
2
- import { options, parse, stringify } from './nako_csv.mjs'
2
+ import { options, parse, stringify, isDelimiterExplicitlySet, setDelimiterExplicitlySet } from './nako_csv.mjs'
3
3
 
4
4
  const PluginCSV = {
5
5
  'meta': {
@@ -26,7 +26,9 @@ const PluginCSV = {
26
26
  josi: [['を', 'の', 'で']],
27
27
  pure: true,
28
28
  fn: function(str: string): (string | number)[][] {
29
- options.delimiter = ','
29
+ if (!isDelimiterExplicitlySet()) {
30
+ options.delimiter = ','
31
+ }
30
32
  return parse(str)
31
33
  }
32
34
  },
@@ -35,8 +37,7 @@ const PluginCSV = {
35
37
  josi: [['を', 'の', 'で']],
36
38
  pure: true,
37
39
  fn: function(str: string): (string|number)[][] {
38
- options.delimiter = '\t'
39
- return parse(str)
40
+ return parse(str, '\t')
40
41
  }
41
42
  },
42
43
  '表CSV変換': { // @二次元配列AをCSV形式に変換して返す // @ひょうCSVへんかん
@@ -44,7 +45,9 @@ const PluginCSV = {
44
45
  josi: [['を']],
45
46
  pure: true,
46
47
  fn: function(a: string[][]): string {
47
- options.delimiter = ','
48
+ if (!isDelimiterExplicitlySet()) {
49
+ options.delimiter = ','
50
+ }
48
51
  return stringify(a)
49
52
  }
50
53
  },
@@ -53,7 +56,9 @@ const PluginCSV = {
53
56
  josi: [['を']],
54
57
  pure: true,
55
58
  fn: function(a: string[][]): string {
56
- options.delimiter = ','
59
+ if (!isDelimiterExplicitlySet()) {
60
+ options.delimiter = ','
61
+ }
57
62
  return stringify(a)
58
63
  }
59
64
  },
@@ -62,8 +67,7 @@ const PluginCSV = {
62
67
  josi: [['を']],
63
68
  pure: true,
64
69
  fn: function(a: string[][]): string {
65
- options.delimiter = '\t'
66
- return stringify(a)
70
+ return stringify(a, '\t')
67
71
  }
68
72
  },
69
73
  'TSV変換': { // @二次元配列AをTSV形式に変換して返す // @TSVへんかん
@@ -71,8 +75,7 @@ const PluginCSV = {
71
75
  josi: [['を']],
72
76
  pure: true,
73
77
  fn: function(a: string[][]): string {
74
- options.delimiter = '\t'
75
- return stringify(a)
78
+ return stringify(a, '\t')
76
79
  }
77
80
  },
78
81
  'CSVオプション設定': { // @「CSV取得」「表CSV変換」命令のオプションOBJ{delimiter,eol,auto_convert_number}をオブジェクトで指定 // @CSVおぷしょんせってい
@@ -84,6 +87,7 @@ const PluginCSV = {
84
87
  const value: unknown = obj[key]
85
88
  if (key === 'delimiter' || key === '区切文字') {
86
89
  options.delimiter = value as string
90
+ setDelimiterExplicitlySet(true)
87
91
  } else if (key === 'eol') {
88
92
  options.eol = value as string
89
93
  } else if (key === 'auto_convert_number') {
@@ -1,5 +1,5 @@
1
1
  // TOMLを読むためのプラグイン
2
- import TOML from 'smol-toml';
2
+ import { parse as tomlParse, stringify as tomlStringify } from 'tomlite';
3
3
  const PluginTOML = {
4
4
  'meta': {
5
5
  type: 'const',
@@ -24,7 +24,7 @@ const PluginTOML = {
24
24
  josi: [['を', 'の', 'から']],
25
25
  pure: true,
26
26
  fn: function (s, _sys) {
27
- return TOML.parse(s);
27
+ return tomlParse(s);
28
28
  }
29
29
  },
30
30
  'TOML変換': {
@@ -32,7 +32,7 @@ const PluginTOML = {
32
32
  josi: [['を', 'から', 'の']],
33
33
  pure: true,
34
34
  fn: function (s, _sys) {
35
- return TOML.stringify(s);
35
+ return tomlStringify(s);
36
36
  }
37
37
  }
38
38
  };