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.
- package/README.md +27 -70
- package/batch/command.txt +26 -805
- package/batch/doctest.mjs +3 -0
- package/batch/doctest.nako3 +514 -0
- package/batch/pickup_command.nako3 +0 -4
- package/core/package-lock.json +17 -20
- package/core/package.json +3 -3
- package/core/src/nako_core_version.mjs +2 -2
- package/core/src/nako_core_version.mts +2 -2
- package/core/src/nako_csv.mjs +9 -0
- package/core/src/nako_csv.mts +12 -0
- package/core/src/nako_gen.mjs +7 -3
- package/core/src/nako_gen.mts +7 -3
- package/core/src/nako_global.mjs +2 -0
- package/core/src/nako_global.mts +4 -0
- package/core/src/nako_parser3.mjs +94 -1
- package/core/src/nako_parser3.mts +69 -1
- package/core/src/nako_read_index.mjs +43 -0
- package/core/src/nako_read_index.mts +43 -0
- package/core/src/plugin_csv.mjs +14 -10
- package/core/src/plugin_csv.mts +14 -10
- package/core/src/plugin_toml.mjs +3 -3
- package/core/src/plugin_toml.mts +3 -3
- package/core/test/array_slice_test.mjs +39 -0
- package/core/test/array_test.mjs +54 -0
- package/core/test/fixtures/compat/SPEC.md +3 -3
- package/core/test/fixtures/compat/cases/04_string.json +1 -4
- package/core/test/fixtures/compat/expected/04_string.json +1 -1
- package/core/test/plugin_csv_test.mjs +20 -0
- package/core/test/plugin_toml_test.mjs +25 -0
- package/core/test/string_index_test.mjs +131 -0
- package/demo/browsers.html +11 -11
- package/doc/browsers.md +11 -11
- package/doc/command_list.json +82 -7236
- package/doc/doctest.md +87 -3
- package/doc/syntax-nako3.md +9 -0
- package/package.json +6 -6
- package/release/_hash.txt +36 -36
- package/release/_script-tags.txt +16 -16
- package/release/command.json +1 -1
- package/release/command.json.js +1 -1
- package/release/command_cnako3.json +1 -1
- package/release/command_list.json +1 -1
- package/release/edit_main.js +8 -8
- package/release/edit_main.js.map +3 -3
- package/release/editor.js +8 -8
- package/release/plugin_caniuse.js +1 -1
- package/release/plugin_caniuse.js.map +2 -2
- package/release/plugin_markup.js +40 -38
- package/release/plugin_markup.js.map +3 -3
- package/release/version.js +1 -1
- package/release/version_main.js +1 -1
- package/release/version_main.js.map +1 -1
- package/release/wnako3.js +192 -226
- package/release/wnako3.js.map +4 -4
- package/release/wnako3webworker.js +180 -243
- package/release/wnako3webworker.js.map +4 -4
- package/src/browsers.mjs +1 -1
- package/src/browsers.txt +31 -31
- package/src/nako_version.mjs +2 -2
- package/src/nako_version.mts +2 -2
- package/src/plugin_browser.mjs +3 -1
- package/src/plugin_browser.mts +3 -1
- package/src/plugin_browser_dialog.mjs +115 -17
- package/src/plugin_browser_dialog.mts +103 -17
- package/src/plugin_browser_dialog_dom.mjs +247 -0
- package/src/plugin_browser_dialog_dom.mts +258 -0
- package/test/common/plugin_browser_ut_audio_test.mjs +6 -7
- package/test/common/plugin_browser_ut_dialog_test.mjs +66 -71
- package/test/common/wnako3mod_test.mjs +6 -0
- package/test/node/build_command_test.mjs +39 -0
- package/test/node/doctest_nako3_test.mjs +112 -0
- package/test/node/fixtures/doctest_nako3/ng.txt +16 -0
- package/test/node/fixtures/doctest_nako3/ok.txt +18 -0
- package/test/node/node_test.mjs +10 -0
- package/test/node/node_version_support_test.mjs +15 -0
- package/test/node/package_json_test.mjs +14 -0
- package/test/node/plugin_browser_dialog_test.mjs +379 -0
- package/test/node/plugin_browser_ut_location_test.mjs +3 -5
package/core/src/nako_csv.mjs
CHANGED
|
@@ -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 判定する文字列
|
package/core/src/nako_csv.mts
CHANGED
|
@@ -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
|
/** 文字列が数値かどうか判定する関数
|
package/core/src/nako_gen.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/core/src/nako_gen.mts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
package/core/src/nako_global.mjs
CHANGED
|
@@ -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('表示ログ', '');
|
package/core/src/nako_global.mts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
}
|
package/core/src/plugin_csv.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/core/src/plugin_csv.mts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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') {
|
package/core/src/plugin_toml.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// TOMLを読むためのプラグイン
|
|
2
|
-
import
|
|
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
|
|
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
|
|
35
|
+
return tomlStringify(s);
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
};
|