nadesiko3 3.3.63 → 3.3.64

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 (44) hide show
  1. package/batch/command.txt +15 -15
  2. package/core/package.json +4 -1
  3. package/core/src/nako3.mjs +5 -2
  4. package/core/src/nako3.mts +5 -4
  5. package/core/src/nako_core_version.mjs +2 -2
  6. package/core/src/nako_core_version.mts +2 -2
  7. package/core/src/nako_from_dncl.mjs +0 -254
  8. package/core/src/nako_from_dncl.mts +0 -247
  9. package/core/src/nako_from_dncl2.mjs +313 -0
  10. package/core/src/nako_from_dncl2.mts +299 -0
  11. package/core/src/nako_gen.mjs +1 -1
  12. package/core/src/nako_gen.mts +1 -1
  13. package/core/src/nako_indent_inline.mjs +152 -34
  14. package/core/src/nako_indent_inline.mts +132 -34
  15. package/core/src/nako_lex_rules.mjs +3 -2
  16. package/core/src/nako_lex_rules.mts +3 -2
  17. package/core/src/nako_lexer.mjs +27 -3
  18. package/core/src/nako_lexer.mts +27 -3
  19. package/core/src/nako_parser3.mjs +45 -14
  20. package/core/src/nako_parser3.mts +40 -18
  21. package/core/src/nako_prepare.mjs +13 -0
  22. package/core/src/nako_prepare.mts +11 -0
  23. package/core/src/nako_tools.mjs +53 -0
  24. package/core/src/nako_tools.mts +46 -0
  25. package/core/test/basic_test.mjs +16 -8
  26. package/core/test/dncl2_test.mjs +207 -0
  27. package/core/test/flow_test.mjs +42 -0
  28. package/core/test/indent_test.mjs +74 -50
  29. package/core/test/inline_indent_test.mjs +47 -0
  30. package/core/test/lex_test.mjs +1 -0
  31. package/demo/js/a.js +30 -0
  32. package/package.json +1 -1
  33. package/release/_hash.txt +16 -16
  34. package/release/_script-tags.txt +14 -14
  35. package/release/nako_gen_async.js +1 -1
  36. package/release/stats.json +1 -1
  37. package/release/version.js +1 -1
  38. package/release/wnako3.js +1 -1
  39. package/release/wnako3webworker.js +1 -1
  40. package/src/cnako3mod.mjs +43 -33
  41. package/src/cnako3mod.mts +45 -35
  42. package/src/nako_version.mjs +2 -2
  43. package/src/nako_version.mts +2 -2
  44. package/src/plugin_browser_canvas.mjs +5 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * DNCL ver2 に対応する構文
3
+ */
4
+ // import { NakoIndentError } from './nako_errors.mjs'
5
+ import { Token, NewEmptyToken } from './nako_types.mjs'
6
+ import { joinTokenLines, splitTokens } from './nako_indent_inline.mjs'
7
+ import { newToken, debugTokens } from './nako_tools.mjs'
8
+
9
+ const IS_DEBUG = false
10
+ const DNCL_ARRAY_INIT_COUNT = 30
11
+
12
+ // DNCL2モードのキーワード
13
+ const DNCL2_KEYWORDS = ['!DNCL2モード', '💡DNCL2モード', '!DNCL2', '💡DNCL2']
14
+
15
+ // 単純な置換チェック
16
+ const DNCL_SIMPLES: { [key: string]: string[] } = {
17
+ '←:←': ['eq', '='],
18
+ '÷:÷': ['÷÷', '÷÷'],
19
+ '{:{': ['[', '['],
20
+ '}:}': [']', ']'],
21
+ 'word:and': ['and', 'かつ'],
22
+ 'word:or': ['or', 'または'],
23
+ 'word:乱数': ['word', '乱数範囲'],
24
+ 'word:表示': ['word', '連続表示']
25
+ }
26
+
27
+ /**
28
+ * DNCLのソースコードをなでしこに変換する
29
+ */
30
+ export function convertDNCL2 (tokens: Token[]): Token[] {
31
+ if (!useDNCL2mode(tokens)) { return tokens }
32
+
33
+ // 一行ずつに分ける
34
+ const lines = splitTokens(tokens, 'eol')
35
+ for (let i = 0; i < lines.length; i++) {
36
+ const line = lines[i]
37
+ if (line.length <= 1) { continue } // 空行は飛ばす
38
+
39
+ // --- 制御構文の変換 ---
40
+ // もし(条件)でないならば → もし(条件)でなければ
41
+ const nai = findTokens(line, ['word:ない'])
42
+ if (nai >= 1) {
43
+ const tt = line[nai]
44
+ if (tt.josi === 'ならば') {
45
+ line[nai - 1].josi = 'でなければ'
46
+ line.splice(nai, 1)
47
+ }
48
+ }
49
+ // そうでなければ(そう|でなければ) → 違えば
50
+ for (let ni = 0; ni < line.length; ni++) {
51
+ const t = line[ni]
52
+ if (t.value === 'そう' && t.josi === 'でなければ') {
53
+ t.type = '違えば'
54
+ t.value = '違えば'
55
+ t.josi = ''
56
+ }
57
+ }
58
+ // 'を実行し,そうでなければ': '違えば',
59
+ for (;;) {
60
+ const ni = findTokens(line, ['word:を実行', 'comma:,', 'word:そう'])
61
+ if (ni < 0) { break }
62
+ const sou = line[ni + 2]
63
+ if (sou.josi === 'でなければ') {
64
+ sou.type = '違えば'
65
+ sou.value = '違えば'
66
+ sou.josi = ''
67
+ line.splice(ni, 3, sou)
68
+ continue
69
+ } else if (sou.josi === 'で') {
70
+ const nakumosi = line[ni + 3]
71
+ if (nakumosi.value.substring(0, 4) === 'なくもし') {
72
+ sou.type = '違えば'
73
+ sou.value = '違えば'
74
+ sou.josi = ''
75
+ line.splice(ni, 3, sou)
76
+ if (nakumosi.value.length > 4) {
77
+ const nakumosiTudukiStr = nakumosi.value.substring(4)
78
+ const nakumosiToken = NewEmptyToken('word', nakumosiTudukiStr, nakumosi.indent, nakumosi.line, nakumosi.file)
79
+ if (nakumosiTudukiStr.match(/^\d/)) { nakumosiToken.type = 'number' }
80
+ line.splice(ni + 2, 0, nakumosiToken)
81
+ nakumosi.value = nakumosi.value.substring(0, 4)
82
+ }
83
+ nakumosi.type = 'もし'
84
+ nakumosi.value = 'もし'
85
+ nakumosi.josi = ''
86
+ continue
87
+ }
88
+ }
89
+ break
90
+ }
91
+
92
+ // Iを1から100まで1(ずつ)|増やしな(が)|ら
93
+ for (;;) {
94
+ const ni = findTokens(line, ['word:増', 'word:ら'])
95
+ if (ni < 0) { break }
96
+ const fu = line[ni]
97
+ fu.type = 'word'
98
+ fu.value = '増繰返'
99
+ fu.josi = ''
100
+ line.splice(ni, 2, fu)
101
+ }
102
+
103
+ // Iを1から100まで1(ずつ)|増やしな(が)|ら
104
+ for (;;) {
105
+ const ni = findTokens(line, ['word:減', 'word:ら'])
106
+ if (ni < 0) { break }
107
+ const fu = line[ni]
108
+ fu.type = 'word'
109
+ fu.value = '減繰返'
110
+ fu.josi = ''
111
+ line.splice(ni, 2, fu)
112
+ }
113
+
114
+ // Iを1から100まで1(ずつ)|増やしな(が)|ら繰り返(す)
115
+ for (;;) {
116
+ const ni = findTokens(line, ['word:増', 'word:ら繰り返'])
117
+ if (ni < 0) { break }
118
+ const fu = line[ni]
119
+ fu.type = 'word'
120
+ fu.value = '増繰返'
121
+ fu.josi = ''
122
+ line.splice(ni, 2, fu)
123
+ }
124
+
125
+ // Iを1から100まで1(ずつ)|増やしな(が)|ら繰り返す
126
+ for (;;) {
127
+ const ni = findTokens(line, ['word:減', 'word:ら繰り返'])
128
+ if (ni < 0) { break }
129
+ const fu = line[ni]
130
+ fu.type = 'word'
131
+ fu.value = '減繰返'
132
+ fu.josi = ''
133
+ line.splice(ni, 2, fu)
134
+ }
135
+
136
+ // --- 配列変数周りの変換 ---
137
+ for (let i = 0; i < line.length; i++) {
138
+ // 配列|Hindoの|すべての|(要素に|値に)|10を|代入する
139
+ if (tokenEq([['word:配列', 'word:配列変数'], 'word', 'word:すべて', ['word:要素', 'word:値'], '*', 'word:代入'], line, i)) {
140
+ const varToken = line[i + 1]
141
+ varToken.josi = ''
142
+ const valToken = line[i + 4]
143
+ valToken.josi = ''
144
+ line.splice(i, 6,
145
+ varToken, newToken('eq', '=', varToken),
146
+ newToken('word', '掛'), newToken('(', '('),
147
+ newToken('[', '['), valToken, newToken(']', ']'), newToken('comma', ','),
148
+ newToken('number', DNCL_ARRAY_INIT_COUNT), newToken(')', ')'))
149
+ i += 6 // skip
150
+ }
151
+ // Hensuの|すべての|(要素を|値を)|0に|する
152
+ if (tokenEq(['word', 'word:すべて', ['word:要素', 'word:値'], ['number', 'string', 'word'], 'word:する'], line, i)) {
153
+ const varToken = line[i]
154
+ varToken.josi = ''
155
+ const valToken = line[i + 3]
156
+ valToken.josi = ''
157
+ line.splice(i, 5,
158
+ varToken, newToken('eq', '=', varToken),
159
+ newToken('word', '掛'), newToken('(', '('),
160
+ newToken('[', '['), valToken, newToken(']', ']'), newToken('comma', ','),
161
+ newToken('number', DNCL_ARRAY_INIT_COUNT), newToken(')', ')'))
162
+ }
163
+ // 配列変数 | xxを | 初期化する
164
+ if (tokenEq([['word:配列変数', 'word:配列'], 'word', 'word:初期化'], line, i)) {
165
+ const varToken = line[i + 1]
166
+ varToken.josi = ''
167
+ line.splice(i, 3,
168
+ varToken, newToken('eq', '=', varToken),
169
+ newToken('word', '掛'), newToken('(', '('),
170
+ newToken('[', '['), newToken('number', 0), newToken(']', ']'), newToken('comma', ','),
171
+ newToken('number', DNCL_ARRAY_INIT_COUNT), newToken(')', ')'))
172
+ }
173
+ }
174
+
175
+ // --- その他の変換 ---
176
+ // 二進で表示 (255) → 二進表示(255)
177
+ for (;;) {
178
+ const ni = findTokens(line, ['word:二進', 'word:表示'])
179
+ if (ni < 0) { break }
180
+ line[ni].value = '二進表示'
181
+ line[ni].josi = ''
182
+ line.splice(ni + 1, 1)
183
+ }
184
+ // '改行なしで表示' → '連続無改行表示'
185
+ for (;;) {
186
+ const ni = findTokens(line, ['word:改行', 'word:表示'])
187
+ if (ni < 0) { break }
188
+ // ここ「改行なしで表示」でも「改行ありで表示」でも同じになってしまう
189
+ // なでしこの制限のため仕方なし
190
+ // 「改行ありで表示」は今のところDNCLに存在しないので無視する
191
+ // もし将来的に区別が必要なら、プリプロセス処理でマクロ的に置換処理を行うことで対応できると思う
192
+ const t = line[ni]
193
+ t.value = '連続無改行表示'
194
+ t.josi = ''
195
+ line.splice(ni + 1, 1)
196
+ }
197
+
198
+ // 一つずつチェック
199
+ let j = 0
200
+ while (j < line.length) {
201
+ const t = line[j]
202
+ // 減と増の分割
203
+ if (t.type === 'word' && t.value.length >= 2) {
204
+ const c = t.value.charAt(t.value.length - 1)
205
+ if (c === '減' || c === '増') {
206
+ t.value = t.value.substring(0, t.value.length - 1)
207
+ t.josi = 'だけ'
208
+ line.splice(j + 1, 0, NewEmptyToken('word', c, t.indent, t.line, t.file))
209
+ }
210
+ j++
211
+ continue
212
+ }
213
+ j++
214
+ }
215
+ }
216
+
217
+ // 最後に単純な置換を行う
218
+ for (let i = 0; i < tokens.length; i++) {
219
+ const t = tokens[i]
220
+ const a = DNCL_SIMPLES[t.type + ':' + t.value]
221
+ if (a !== undefined) {
222
+ t.type = a[0]
223
+ t.value = a[1]
224
+ }
225
+ }
226
+ tokens = joinTokenLines(lines)
227
+ if (IS_DEBUG) {
228
+ console.log('@@@---DNCL:tokens---')
229
+ console.log(debugTokens(tokens))
230
+ console.log('@@@/---DNCL:tokens---')
231
+ }
232
+ return tokens
233
+ }
234
+
235
+ /**
236
+ * トークンが合致するかを確認する
237
+ * @param typeValues ['word:それ']のようなタイプ名と値の配列/'*'でワイルドカードが使える/":"がなればタイプだけ確認/配列で選択
238
+ * @param lines 差し替え
239
+ * @param fromIndex 検索場所
240
+ * @returns 合致したかどうか
241
+ */
242
+ function tokenEq (typeValues: any[], lines: Token[], fromIndex: number): boolean {
243
+ const check = (pattern: string|Array<string>, t: Token): boolean => {
244
+ if (pattern instanceof Array) {
245
+ for (let i = 0; i < pattern.length; i++) {
246
+ if (check(pattern[i], t)) { return true }
247
+ }
248
+ return false
249
+ }
250
+ if (pattern === '*') { return true }
251
+ if (pattern.indexOf(':') < 0) {
252
+ if (pattern === t.type) { return true } else { return false }
253
+ }
254
+ const tv = `${t.type}:${t.value}`
255
+ if (pattern === tv) { return true }
256
+ return false
257
+ }
258
+ for (let i = 0; i < typeValues.length; i++) {
259
+ const idx = i + fromIndex
260
+ if (idx >= lines.length) { return false }
261
+ const pat = typeValues[i]
262
+ const t = lines[idx]
263
+ if (!check(pat, t)) { return false }
264
+ }
265
+ return true
266
+ }
267
+
268
+ function findTokens (tokens: Token[], findTypeValue: string[]): number {
269
+ const findA = findTypeValue.map(s => s.split(':'))
270
+ for (let i = 0; i < tokens.length; i++) {
271
+ let flag = true
272
+ for (let j = 0; j < findA.length; j++) {
273
+ const f = findA[j]
274
+ const idx = i + j
275
+ if (idx >= tokens.length) { return -1 }
276
+ if (tokens[idx].type === f[0] && tokens[idx].value === f[1]) {
277
+ continue
278
+ } else {
279
+ flag = false
280
+ break
281
+ }
282
+ }
283
+ if (flag) { return i }
284
+ }
285
+ return -1
286
+ }
287
+
288
+ function useDNCL2mode (tokens: Token[]): boolean {
289
+ // 先頭の100語調べる
290
+ for (let i = 0; i < tokens.length; i++) {
291
+ if (i > 100) { break }
292
+ const t = tokens[i]
293
+ if (t.type === 'line_comment' && DNCL2_KEYWORDS.indexOf(t.value) >= 0) {
294
+ t.type = 'DNCL2モード'
295
+ return true
296
+ }
297
+ }
298
+ return false
299
+ }
@@ -823,7 +823,7 @@ export class NakoGen {
823
823
  // codeInit?
824
824
  if (node.checkInit) {
825
825
  const arrayDefCode = '[0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0]';
826
- codeInit += `\n/*配列初期化*/if (!(${name} instanceof Array)) { ${name} = ${arrayDefCode}; console.log('初期化:${name}') };`;
826
+ codeInit += `\n/*配列初期化*/if (!(${name} instanceof Array)) { ${name} = ${arrayDefCode}; /*console.log('初期化:${name}')*/ };`;
827
827
  for (let i = 0; i < list.length - 1; i++) {
828
828
  const idx = this._convGen(list[i], true);
829
829
  codeArray += `[${idx}]`;
@@ -886,7 +886,7 @@ export class NakoGen {
886
886
  // codeInit?
887
887
  if (node.checkInit) {
888
888
  const arrayDefCode = '[0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0]'
889
- codeInit += `\n/*配列初期化*/if (!(${name} instanceof Array)) { ${name} = ${arrayDefCode}; console.log('初期化:${name}') };`
889
+ codeInit += `\n/*配列初期化*/if (!(${name} instanceof Array)) { ${name} = ${arrayDefCode}; /*console.log('初期化:${name}')*/ };`
890
890
  for (let i = 0; i < list.length - 1; i++) {
891
891
  const idx = this._convGen(list[i], true)
892
892
  codeArray += `[${idx}]`
@@ -1,6 +1,8 @@
1
1
  /** インデント構文を処理するモジュール */
2
2
  import { NewEmptyToken } from './nako_types.mjs';
3
3
  import { NakoIndentError } from '../src/nako_errors.mjs';
4
+ import { debugTokens, newToken } from './nako_tools.mjs';
5
+ const IS_DEBUG = false;
4
6
  function isSkipWord(t) {
5
7
  if (t.type === '違えば') {
6
8
  return true;
@@ -12,28 +14,62 @@ function isSkipWord(t) {
12
14
  }
13
15
  /** インラインインデント構文 --- 末尾の":"をインデントを考慮して"ここまで"を挿入 (#1215) */
14
16
  export function convertInlineIndent(tokens) {
17
+ //
18
+ // 0:もし、A=0ならば:
19
+ // 2: もし、B=0ならば:
20
+ // 4: 「A=0,B=0」を表示。
21
+ // 2: 違えば:
22
+ // 4: 「A=0,B!=0」を表示。
23
+ // 5:違えば:
24
+ // 6: 「A!=0」を表示。
25
+ //
15
26
  const lines = splitTokens(tokens, 'eol');
16
27
  const blockIndents = [];
17
28
  let checkICount = -1;
29
+ let jsonObjLevel = 0;
30
+ let jsonArrayLevel = 0;
31
+ const checkJsonSyntax = (line) => {
32
+ // JSONのオブジェクトがあるか?
33
+ line.forEach((t) => {
34
+ if (t.type === '{') {
35
+ jsonObjLevel++;
36
+ }
37
+ if (t.type === '}') {
38
+ jsonObjLevel--;
39
+ }
40
+ if (t.type === '[') {
41
+ jsonArrayLevel++;
42
+ }
43
+ if (t.type === ']') {
44
+ jsonArrayLevel--;
45
+ }
46
+ });
47
+ };
18
48
  for (let i = 0; i < lines.length; i++) {
19
49
  const line = lines[i];
20
- if (line.length === 0) {
50
+ // 空行は飛ばす || コメント行だけの行も飛ばす
51
+ if (IsEmptyLine(line)) {
21
52
  continue;
22
53
  }
23
- if (line[0].type === 'eol') {
54
+ const leftToken = GetLeftTokens(line);
55
+ // JSONの途中であればブロックの変更は行わない
56
+ if (jsonObjLevel > 0 || jsonArrayLevel > 0) {
57
+ checkJsonSyntax(line);
24
58
  continue;
25
59
  }
26
60
  // インデントの終了を確認する必要があるか?
27
61
  if (checkICount >= 0) {
28
- const lineICount = lines[i][0].indent;
62
+ const lineICount = leftToken.indent;
29
63
  while (checkICount >= lineICount) {
30
- const tFirst = line[0];
31
- if (isSkipWord(tFirst)) { // 「違えば」の直前には「ここまで」不要
32
- // 挿入不要
64
+ const tFirst = leftToken;
65
+ // console.log('@@', lineICount, '>>', checkICount, tFirst.type)
66
+ if (isSkipWord(tFirst) && (checkICount === lineICount)) { // 「違えば」や「エラーならば」
67
+ // 「ここまで」の挿入不要 / ただしネストした際の「違えば」(上記の5の状態なら必要)
33
68
  }
34
69
  else {
35
70
  // ここまでを挿入する
36
- lines[i - 1].push(NewEmptyToken('ここまで', '', lineICount, tFirst.line));
71
+ lines[i - 1].push(newToken('ここまで', 'ここまで', tFirst));
72
+ lines[i - 1].push(newToken('eol', '\n', tFirst));
37
73
  }
38
74
  blockIndents.pop();
39
75
  if (blockIndents.length > 0) {
@@ -45,6 +81,12 @@ export function convertInlineIndent(tokens) {
45
81
  }
46
82
  }
47
83
  }
84
+ // JSONの途中であればブロックの変更は行わない
85
+ checkJsonSyntax(line);
86
+ if (jsonObjLevel > 0 || jsonArrayLevel > 0) {
87
+ continue;
88
+ }
89
+ // 末尾の「:」をチェック
48
90
  const tLast = getLastTokenWithoutEOL(line);
49
91
  if (tLast.type === ':') {
50
92
  // 末尾の「:」を削除
@@ -53,12 +95,27 @@ export function convertInlineIndent(tokens) {
53
95
  blockIndents.push(checkICount);
54
96
  }
55
97
  }
56
- if (lines.length > 0) {
98
+ if (lines.length > 0 && blockIndents.length > 0) {
99
+ // トークン情報を得るため、直近のトークンを得る
100
+ let t = tokens[0];
101
+ for (let i = lines.length - 1; i >= 0; i--) {
102
+ const line = lines[i];
103
+ if (line.length > 0) {
104
+ t = line[line.length - 1];
105
+ break;
106
+ }
107
+ }
108
+ // ここまでを差し込む
57
109
  for (let i = 0; i < blockIndents.length; i++) {
58
- lines[lines.length - 1].push(NewEmptyToken('ここまで'));
110
+ lines[lines.length - 1].push(newToken('ここまで', 'ここまで', t));
111
+ lines[lines.length - 1].push(newToken('eol', '\n', t));
59
112
  }
60
113
  }
61
- return joinTokenLines(lines);
114
+ const result = joinTokenLines(lines);
115
+ if (IS_DEBUG) {
116
+ console.log('###', debugTokens(result));
117
+ }
118
+ return result;
62
119
  }
63
120
  /** 行ごとに分割していたトークンをくっつける */
64
121
  export function joinTokenLines(lines) {
@@ -67,19 +124,9 @@ export function joinTokenLines(lines) {
67
124
  for (const t of line) {
68
125
  r.push(t);
69
126
  }
70
- // debug
71
- // console.log('@@join=', mkIndent(line[0] ? line[0].indent : 0), line.map(t => (t.type + '_' + t.value + t.josi + ':' + t.indent)).join(' | '))
72
127
  }
73
- // console.log('@@@-----')
74
128
  return r;
75
129
  }
76
- function mkIndent(num) {
77
- let s = '';
78
- for (let i = 0; i < num; i++) {
79
- s += ' ';
80
- }
81
- return s;
82
- }
83
130
  function getLastTokenWithoutEOL(line) {
84
131
  const len = line.length;
85
132
  if (len === 0) {
@@ -115,6 +162,31 @@ export function splitTokens(tokens, delimiter) {
115
162
  }
116
163
  return result;
117
164
  }
165
+ /** トークン行が空かどうか調べる */
166
+ function IsEmptyLine(line) {
167
+ if (line.length === 0) {
168
+ return true;
169
+ }
170
+ for (let j = 0; j < line.length; j++) {
171
+ const ty = line[j].type;
172
+ if (ty === 'eol' || ty === 'line_comment' || ty === 'range_comment') {
173
+ continue;
174
+ }
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ /** コメントを除去した最初のトークンを返す */
180
+ function GetLeftTokens(line) {
181
+ for (let i = 0; i < line.length; i++) {
182
+ const t = line[i].type;
183
+ if (t === 'eol' || t === 'line_comment' || t === 'range_comment') {
184
+ continue;
185
+ }
186
+ return line[i];
187
+ }
188
+ return line[0];
189
+ }
118
190
  // インデント構文のキーワード
119
191
  const INDENT_MODE_KEYWORDS = ['!インデント構文', '!ここまでだるい', '💡インデント構文', '💡ここまでだるい'];
120
192
  /** インデント構文 --- インデントを見て"ここまで"を自動挿入 (#596) */
@@ -130,18 +202,44 @@ export function convertIndentSyntax(tokens) {
130
202
  throw new NakoIndentError('インデント構文が有効化されているときに『ここまで』を使うことはできません。', t.line, t.file);
131
203
  }
132
204
  }
205
+ // JSON構文のチェック
206
+ let jsonObjLevel = 0;
207
+ let jsonArrayLevel = 0;
208
+ const checkJsonSyntax = (line) => {
209
+ // JSONのオブジェクトがあるか?
210
+ line.forEach((t) => {
211
+ if (t.type === '{') {
212
+ jsonObjLevel++;
213
+ }
214
+ if (t.type === '}') {
215
+ jsonObjLevel--;
216
+ }
217
+ if (t.type === '[') {
218
+ jsonArrayLevel++;
219
+ }
220
+ if (t.type === ']') {
221
+ jsonArrayLevel--;
222
+ }
223
+ });
224
+ };
225
+ // 行ごとにトークンを分割
133
226
  const blockIndents = [];
134
227
  const lines = splitTokens(tokens, 'eol');
135
228
  let lastI = 0;
229
+ // 各行を確認する
136
230
  for (let i = 0; i < lines.length; i++) {
137
231
  const line = lines[i];
138
- if (line.length === 0) {
232
+ // 空行は飛ばす || コメント行だけの行も飛ばす
233
+ if (IsEmptyLine(line)) {
139
234
  continue;
140
- } // 空行は飛ばす
141
- if (line[0].type === 'eol') {
235
+ }
236
+ // JSON構文のチェック
237
+ if (jsonArrayLevel > 0 || jsonObjLevel > 0) {
238
+ checkJsonSyntax(line);
142
239
  continue;
143
- } // 空行は飛ばす
144
- const curI = line[0].indent;
240
+ }
241
+ const leftToken = GetLeftTokens(line);
242
+ const curI = leftToken.indent;
145
243
  if (curI === lastI) {
146
244
  continue;
147
245
  }
@@ -155,16 +253,19 @@ export function convertIndentSyntax(tokens) {
155
253
  // ブロックの終了?
156
254
  if (lastI >= 0) {
157
255
  while (lastI > curI) {
158
- if (isSkipWord(line[0])) {
159
- // 「違えば」などなら不要
256
+ const blockIndentTopLast = blockIndents[blockIndents.length - 1][1];
257
+ // console.log('@@[', i, ']', lastI, '>', curI, '@', blockIndentTopLast, leftToken.type)
258
+ if (isSkipWord(leftToken) && blockIndentTopLast === curI) {
259
+ // 「違えば」などなら不要 (ただし、違えばがネストしている場合は必要)
160
260
  }
161
261
  else {
162
- lines[i - 1].push(NewEmptyToken('ここまで'));
262
+ const t = lines[i - 1][0];
263
+ lines[i - 1].push(newToken('ここまで', 'ここまで', t));
264
+ lines[i - 1].push(newToken('eol', '\n', t));
163
265
  }
164
- // console.log('@@@pop', lastI, '>=', curI, ':', line[0])
165
266
  blockIndents.pop();
166
267
  if (blockIndents.length > 0) {
167
- lastI = blockIndents[blockIndents.length - 1];
268
+ lastI = blockIndents[blockIndents.length - 1][0];
168
269
  }
169
270
  else {
170
271
  lastI = 0;
@@ -172,19 +273,36 @@ export function convertIndentSyntax(tokens) {
172
273
  }
173
274
  }
174
275
  }
276
+ if (jsonArrayLevel > 0 || jsonObjLevel > 0) {
277
+ continue;
278
+ }
279
+ // JSON構文のチェック
280
+ checkJsonSyntax(line);
175
281
  // ブロックの開始?
176
282
  if (curI > lastI) {
177
- blockIndents.push(curI);
283
+ blockIndents.push([curI, lastI]);
178
284
  // console.log('@@@push', curI)
179
285
  lastI = curI;
180
286
  continue;
181
287
  }
182
288
  }
289
+ // 末尾に「ここまで」を追加する
183
290
  for (let i = 0; i < blockIndents.length; i++) {
184
- lines[lines.length - 1].push(NewEmptyToken('ここまで'));
291
+ // トークン情報を得るため、直近のトークンを得る
292
+ let t = tokens[0];
293
+ for (let i = lines.length - 1; i >= 0; i--) {
294
+ const line = lines[i];
295
+ if (line.length > 0) {
296
+ t = line[line.length - 1];
297
+ break;
298
+ }
299
+ }
300
+ lines[lines.length - 1].push(newToken('ここまで', 'ここまで', t));
301
+ lines[lines.length - 1].push(newToken('eol', '\n', t));
185
302
  }
186
- // 再構築
187
- return joinTokenLines(lines);
303
+ const result = joinTokenLines(lines);
304
+ // console.log('###', debugTokens(result))
305
+ return result;
188
306
  }
189
307
  function useIndentSynax(tokens) {
190
308
  // インデント構文が必要かチェック (最初の100個をチェック)