nadesiko3 3.7.19 → 3.7.20

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/batch/command.txt CHANGED
@@ -789,10 +789,10 @@
789
789
  | 関数 | フォルダ作成 | PATHの/PATHを/PATHに/PATHへ | ディレクトリPATHを作成して返す(再帰的に作成) | ふぉるださくせい | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L682
790
790
  | 変数 | ファイルコピーデフォルト動作 | | '上書禁止' | ふぁいるこぴーでふぉるとどうさ | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L686
791
791
  | 関数 | ファイルコピー | AからBに/AをBへ | パスAをパスBへファイルコピーする(『ファイルコピーデフォルト動作』が「上書」「上書き」「overwrite」なら上書きコピー、それ以外はコピー先が存在するなら失敗)(非同期関数) | ふぁいるこぴー | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L692
792
- | 関数 | ファイル上書コピー | AからBに/AをBへ | パスAをパスBへファイルコピーする(相手先に内容をマージしてコピー)(非同期関数) | ふぁいるうわがきこぴー | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L707
792
+ | 関数 | ファイル上書コピー | AからBに/AをBへ | パスAをパスBへファイルコピーする(コピー先に内容をマージしてコピー)(非同期関数) | ふぁいるうわがきこぴー | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L707
793
793
  | 関数 | ファイルコピー時 | CALLBACKでAからBに/AをBへ | パスAをパスBへファイルコピーしてcallbackを実行 | ふぁいるこぴーしたとき | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L717
794
794
  | 関数 | ファイル移動 | AからBに/AをBへ | パスAをパスBへ移動する(『ファイルコピーデフォルト動作』が「上書」「上書き」「overwrite」なら上書き移動、それ以外は移動先が存在するなら失敗)(非同期関数) | ふぁいるいどう | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L730
795
- | 関数 | ファイル上書移動 | AからBに/AをBへ | パスAをパスBへ移動する(相手先に内容をマージしてコピー後、元を削除)(非同期関数) | ふぁいるうわがきいどう | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L748
795
+ | 関数 | ファイル上書移動 | AからBに/AをBへ | パスAをパスBへ移動する(移動先に内容をマージしてコピー後、元を削除)(非同期関数) | ふぁいるうわがきいどう | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L748
796
796
  | 関数 | ファイル処理時 | Fを/Fで | ファイルコピー・移動の処理状況をコールバックFで報告する(変数『対象』に{"件数":N,"現在":M}をセット) | ふぁいるしょりじ | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L761
797
797
  | 関数 | ファイル処理強制停止 | | 『ファイルコピー』『ファイル移動』などの処理を強制停止する | ふぁいるしょりきょうせいていし | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L773
798
798
  | 関数 | ファイル移動時 | CALLBACKでAからBに/AをBへ | パスAをパスBへ移動してcallbackを実行 | ふぁいるいどうしたとき | https://github.com/kujirahand/nadesiko3/tree/master/src/plugin_node.mts#L782
package/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nadesiko3core",
3
- "version": "3.7.19",
3
+ "version": "3.7.20",
4
4
  "description": "Japanese Programming Language Nadesiko v3 core",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
@@ -45,6 +45,7 @@ export type NodeType = 'nop'
45
45
  | 'inc'
46
46
  | 'func'
47
47
  | 'calc_func'
48
+ | 'call_value'
48
49
  | 'func_pointer'
49
50
  | 'func_obj'
50
51
  | 'renbun'
@@ -1,8 +1,8 @@
1
1
  // 実際のバージョン定義 (自動生成されるので以下を編集しない)
2
2
  const coreVersion = {
3
- version: '3.7.19',
3
+ version: '3.7.20',
4
4
  major: 3,
5
5
  minor: 7,
6
- patch: 19
6
+ patch: 20
7
7
  };
8
8
  export default coreVersion;
@@ -11,9 +11,9 @@ export interface NakoCoreVersion {
11
11
  }
12
12
  // 実際のバージョン定義 (自動生成されるので以下を編集しない)
13
13
  const coreVersion: NakoCoreVersion = {
14
- version: '3.7.19',
14
+ version: '3.7.20',
15
15
  major: 3,
16
16
  minor: 7,
17
- patch: 19
17
+ patch: 20
18
18
  }
19
19
  export default coreVersion
@@ -65,6 +65,7 @@ export class NakoGen {
65
65
  /** スタックトップ */
66
66
  this.varsSet = { isFunction: false, names: new Set(), readonly: new Set() };
67
67
  this.varslistSet[2] = this.varsSet;
68
+ this.functionContextStack = [];
68
69
  // 現在定義中の関数名
69
70
  this.defFuncName = '';
70
71
  // 1以上のとき高速化する。
@@ -249,6 +250,11 @@ export class NakoGen {
249
250
  code += 'const __v0 = __self.__v0 = __self.__varslist[0];\n';
250
251
  code += 'const __v1 = __self.__v1 = __self.__varslist[1];\n';
251
252
  code += 'const __vars = __self.__vars = __self.__varslist[2];\n';
253
+ code += 'const __nako_make_closure = (local, parent) => ({\n' +
254
+ ' has: (key) => local.has(key) || (parent !== null && parent.has(key)),\n' +
255
+ ' get: (key) => local.has(key) ? local.get(key) : (parent !== null ? parent.get(key) : undefined),\n' +
256
+ ' set: (key, value) => { if (local.has(key) || parent === null || !parent.has(key)) { local.set(key, value); } else { parent.set(key, value); } return value; }\n' +
257
+ '});\n';
252
258
  code += `const __modList = __self.__modList = ${JSON.stringify(com.getModList())}\n`;
253
259
  code += 'const __line = (lineno) => { __self.__v0.set(\'__line\', lineno); }\n';
254
260
  code += '__v0.set(\'__line\', \'l0:__getDefFuncCode\');\n';
@@ -491,6 +497,9 @@ export class NakoGen {
491
497
  case 'calc_func':
492
498
  code += this.convCallFunc(node, isExpression);
493
499
  break;
500
+ case 'call_value':
501
+ code += this.convCallValue(node, isExpression);
502
+ break;
494
503
  case 'if':
495
504
  code += this.convIf(node);
496
505
  break;
@@ -574,6 +583,28 @@ export class NakoGen {
574
583
  js_set: this.varname_set(name, String(jsvalue))
575
584
  };
576
585
  }
586
+ // 外側の無名関数のローカル変数をクロージャとして参照する #2268
587
+ const currentFunc = this.functionContextStack[this.functionContextStack.length - 1];
588
+ if (this.varslistSet.length > 4 && currentFunc?.isAnonymous) {
589
+ const currentIndex = this.varslistSet.length - 1;
590
+ for (let i = currentIndex - 1; i >= 3; i--) {
591
+ if (this.varslistSet[i].names.has(name)) {
592
+ for (const context of this.functionContextStack) {
593
+ if (context.isAnonymous && context.varsIndex > i) {
594
+ context.usesClosure = true;
595
+ }
596
+ }
597
+ const nameJson = JSON.stringify(name);
598
+ return {
599
+ i,
600
+ name,
601
+ isTop: false,
602
+ js: `__nako_closure.get(${nameJson})`,
603
+ js_set: `__nako_closure.set(${nameJson}, ${jsvalue ?? 'undefined'})`
604
+ };
605
+ }
606
+ }
607
+ }
577
608
  // __varslist ?
578
609
  for (let i = 2; i >= 0; i--) {
579
610
  if (this.varslistSet[i].names.has(name)) {
@@ -787,6 +818,12 @@ export class NakoGen {
787
818
  this.varsSet = { isFunction: true, names: initialNames, readonly: new Set() };
788
819
  // ローカル変数をPUSHする
789
820
  this.varslistSet.push(this.varsSet);
821
+ const funcContext = {
822
+ isAnonymous: name === '',
823
+ varsIndex: this.varslistSet.length - 1,
824
+ usesClosure: false
825
+ };
826
+ this.functionContextStack.push(funcContext);
790
827
  // JSの引数と引数をバインド
791
828
  if (isExtractJS) {
792
829
  variableDeclarations += indent + 'var 引数 = arguments;\n';
@@ -903,6 +940,10 @@ export class NakoGen {
903
940
  const lineInfo = ' ' + this.convLineno(node, true, 1) + '\n';
904
941
  code = tof + performanceMonitorInjectAtStart + pushStack + variableDeclarations + lineInfo + code + popStack;
905
942
  code += endOfFunction;
943
+ if (funcContext.isAnonymous && funcContext.usesClosure) {
944
+ const parentClosure = `(typeof __nako_closure === 'undefined' ? null : __nako_closure)`;
945
+ code = `(function(__nako_closure) {\n return ${code}\n})(__nako_make_closure(__self.__vars, ${parentClosure}))`;
946
+ }
906
947
  // 名前があれば、関数を登録する
907
948
  if (name) {
908
949
  const nameFuncValue = this.nakoFuncList.get(name);
@@ -914,6 +955,7 @@ export class NakoGen {
914
955
  }
915
956
  this.usedAsyncFn = oldUsedAsyncFn; // 以前の値を戻す
916
957
  this.varslistSet.pop();
958
+ this.functionContextStack.pop();
917
959
  this.varsSet = this.varslistSet[this.varslistSet.length - 1];
918
960
  if (name) {
919
961
  this.__self.__varslist[1].set(name, code);
@@ -1650,6 +1692,18 @@ export class NakoGen {
1650
1692
  }
1651
1693
  return code;
1652
1694
  }
1695
+ convCallValue(node, isExpression) {
1696
+ const callee = this._convGen(node.blocks[0], true);
1697
+ const args = node.blocks.slice(1).map((arg) => this._convGen(arg, true));
1698
+ args.push('__self');
1699
+ const funcCall = `${callee}(${args.join(',')})`;
1700
+ if (isExpression) {
1701
+ return funcCall;
1702
+ }
1703
+ const sorePrefex = (this.speedMode.invalidSore === 0) ? '__self.__setSore(' : '';
1704
+ const sorePostfix = (this.speedMode.invalidSore === 0) ? ')' : '';
1705
+ return this.convLineno(node, false) + `${sorePrefex}${funcCall}${sorePostfix};\n`;
1706
+ }
1653
1707
  convRenbun(node) {
1654
1708
  const right = this._convGen(node.blocks[1], true);
1655
1709
  const left = this._convGen(node.blocks[0], false);
@@ -32,6 +32,11 @@ interface PerformanceMonitor {
32
32
  systemFunctionBody: number; // システム関数(呼び出しコードを除く)
33
33
  mumeiId: number;
34
34
  }
35
+ interface FunctionContext {
36
+ isAnonymous: boolean;
37
+ varsIndex: number;
38
+ usesClosure: boolean;
39
+ }
35
40
  interface FindVarResult {
36
41
  i: number;
37
42
  name: string;
@@ -80,6 +85,7 @@ export class NakoGen {
80
85
  // 変数管理
81
86
  private varslistSet: VarsSet[] // [システム変数一覧, グローバル変数一覧, ローカル変数一覧]で変数セットを記録
82
87
  private varsSet: VarsSet // ローカルな変数を記録
88
+ private functionContextStack: FunctionContext[] // 関数生成時のレキシカルスコープ情報
83
89
  public debugOption: NakoDebugOption
84
90
  // public
85
91
  numAsyncFn: number
@@ -140,6 +146,7 @@ export class NakoGen {
140
146
  /** スタックトップ */
141
147
  this.varsSet = { isFunction: false, names: new Set(), readonly: new Set() }
142
148
  this.varslistSet[2] = this.varsSet
149
+ this.functionContextStack = []
143
150
 
144
151
  // 現在定義中の関数名
145
152
  this.defFuncName = ''
@@ -333,6 +340,11 @@ export class NakoGen {
333
340
  code += 'const __v0 = __self.__v0 = __self.__varslist[0];\n'
334
341
  code += 'const __v1 = __self.__v1 = __self.__varslist[1];\n'
335
342
  code += 'const __vars = __self.__vars = __self.__varslist[2];\n'
343
+ code += 'const __nako_make_closure = (local, parent) => ({\n' +
344
+ ' has: (key) => local.has(key) || (parent !== null && parent.has(key)),\n' +
345
+ ' get: (key) => local.has(key) ? local.get(key) : (parent !== null ? parent.get(key) : undefined),\n' +
346
+ ' set: (key, value) => { if (local.has(key) || parent === null || !parent.has(key)) { local.set(key, value); } else { parent.set(key, value); } return value; }\n' +
347
+ '});\n'
336
348
  code += `const __modList = __self.__modList = ${JSON.stringify(com.getModList())}\n`
337
349
  code += 'const __line = (lineno) => { __self.__v0.set(\'__line\', lineno); }\n'
338
350
  code += '__v0.set(\'__line\', \'l0:__getDefFuncCode\');\n'
@@ -572,6 +584,9 @@ export class NakoGen {
572
584
  case 'calc_func':
573
585
  code += this.convCallFunc(node as AstCallFunc, isExpression)
574
586
  break
587
+ case 'call_value':
588
+ code += this.convCallValue(node as AstBlocks, isExpression)
589
+ break
575
590
  case 'if':
576
591
  code += this.convIf(node as AstIf)
577
592
  break
@@ -656,6 +671,28 @@ export class NakoGen {
656
671
  js_set: this.varname_set(name, String(jsvalue))
657
672
  }
658
673
  }
674
+ // 外側の無名関数のローカル変数をクロージャとして参照する #2268
675
+ const currentFunc = this.functionContextStack[this.functionContextStack.length - 1]
676
+ if (this.varslistSet.length > 4 && currentFunc?.isAnonymous) {
677
+ const currentIndex = this.varslistSet.length - 1
678
+ for (let i = currentIndex - 1; i >= 3; i--) {
679
+ if (this.varslistSet[i].names.has(name)) {
680
+ for (const context of this.functionContextStack) {
681
+ if (context.isAnonymous && context.varsIndex > i) {
682
+ context.usesClosure = true
683
+ }
684
+ }
685
+ const nameJson = JSON.stringify(name)
686
+ return {
687
+ i,
688
+ name,
689
+ isTop: false,
690
+ js: `__nako_closure.get(${nameJson})`,
691
+ js_set: `__nako_closure.set(${nameJson}, ${jsvalue ?? 'undefined'})`
692
+ }
693
+ }
694
+ }
695
+ }
659
696
  // __varslist ?
660
697
  for (let i = 2; i >= 0; i--) {
661
698
  if (this.varslistSet[i].names.has(name)) {
@@ -872,6 +909,12 @@ export class NakoGen {
872
909
  this.varsSet = { isFunction: true, names: initialNames, readonly: new Set() }
873
910
  // ローカル変数をPUSHする
874
911
  this.varslistSet.push(this.varsSet)
912
+ const funcContext: FunctionContext = {
913
+ isAnonymous: name === '',
914
+ varsIndex: this.varslistSet.length - 1,
915
+ usesClosure: false
916
+ }
917
+ this.functionContextStack.push(funcContext)
875
918
  // JSの引数と引数をバインド
876
919
  if (isExtractJS) {
877
920
  variableDeclarations += indent + 'var 引数 = arguments;\n'
@@ -978,6 +1021,10 @@ export class NakoGen {
978
1021
  const lineInfo = ' ' + this.convLineno(node, true, 1) + '\n'
979
1022
  code = tof + performanceMonitorInjectAtStart + pushStack + variableDeclarations + lineInfo + code + popStack
980
1023
  code += endOfFunction
1024
+ if (funcContext.isAnonymous && funcContext.usesClosure) {
1025
+ const parentClosure = `(typeof __nako_closure === 'undefined' ? null : __nako_closure)`
1026
+ code = `(function(__nako_closure) {\n return ${code}\n})(__nako_make_closure(__self.__vars, ${parentClosure}))`
1027
+ }
981
1028
 
982
1029
  // 名前があれば、関数を登録する
983
1030
  if (name) {
@@ -991,6 +1038,7 @@ export class NakoGen {
991
1038
  this.usedAsyncFn = oldUsedAsyncFn // 以前の値を戻す
992
1039
 
993
1040
  this.varslistSet.pop()
1041
+ this.functionContextStack.pop()
994
1042
  this.varsSet = this.varslistSet[this.varslistSet.length - 1]
995
1043
  if (name) { this.__self.__varslist[1].set(name, code) }
996
1044
  this.defFuncName = '' // 関数名をクリア
@@ -1727,6 +1775,19 @@ export class NakoGen {
1727
1775
  return code
1728
1776
  }
1729
1777
 
1778
+ convCallValue(node: AstBlocks, isExpression: boolean): string {
1779
+ const callee = this._convGen(node.blocks[0], true)
1780
+ const args = node.blocks.slice(1).map((arg) => this._convGen(arg, true))
1781
+ args.push('__self')
1782
+ const funcCall = `${callee}(${args.join(',')})`
1783
+ if (isExpression) {
1784
+ return funcCall
1785
+ }
1786
+ const sorePrefex = (this.speedMode.invalidSore === 0) ? '__self.__setSore(' : ''
1787
+ const sorePostfix = (this.speedMode.invalidSore === 0) ? ')' : ''
1788
+ return this.convLineno(node, false) + `${sorePrefex}${funcCall}${sorePostfix};\n`
1789
+ }
1790
+
1730
1791
  convRenbun(node: AstOperator): string {
1731
1792
  const right = this._convGen(node.blocks[1], true)
1732
1793
  const left = this._convGen(node.blocks[0], false)
@@ -907,7 +907,7 @@ export class NakoParser extends NakoParserBase {
907
907
  }
908
908
  return ans;
909
909
  }
910
- yGetArgParen(y) {
910
+ yGetArgParen(y, funcName) {
911
911
  let isClose = false;
912
912
  const si = this.stack.length;
913
913
  while (!this.isEOF()) {
@@ -927,7 +927,8 @@ export class NakoParser extends NakoParserBase {
927
927
  break;
928
928
  }
929
929
  if (!isClose) {
930
- throw NakoSyntaxError.fromNode(`C風関数『${y[0].value}』でカッコが閉じていません`, y[0]);
930
+ const name = funcName || y[0].value || this.nodeToStr(y[0], { depth: 0, typeName: '関数' }, false);
931
+ throw NakoSyntaxError.fromNode(`C風関数『${name}』でカッコが閉じていません`, y[0]);
931
932
  }
932
933
  const a = [];
933
934
  while (si < this.stack.length) {
@@ -938,6 +939,30 @@ export class NakoParser extends NakoParserBase {
938
939
  }
939
940
  return a;
940
941
  }
942
+ /** 関数の戻り値を続けてC風呼び出しする */
943
+ yApplyCallValue(callee) {
944
+ let node = callee;
945
+ while (this.check('(')) {
946
+ this.get(); // skip '('
947
+ const args = this.yGetArgParen([node], '関数呼び出しの結果');
948
+ if (!this.check(')')) {
949
+ throw NakoSyntaxError.fromNode('C風関数呼び出しのエラー', node);
950
+ }
951
+ const close = this.get();
952
+ node = {
953
+ type: 'call_value',
954
+ blocks: [node, ...args],
955
+ josi: close?.josi || '',
956
+ startOffset: node.startOffset,
957
+ endOffset: close?.endOffset,
958
+ line: node.line,
959
+ column: node.column,
960
+ file: node.file,
961
+ end: this.peekSourceMap()
962
+ };
963
+ }
964
+ return node;
965
+ }
941
966
  /** @returns {AstRepeatTimes | null} */
942
967
  yRepeatTime() {
943
968
  const map = this.peekSourceMap();
@@ -1775,7 +1800,7 @@ export class NakoParser extends NakoParserBase {
1775
1800
  }
1776
1801
  // 「**して、**」の場合も一度切る
1777
1802
  if (RenbunJosi.indexOf(t.josi) >= 0) {
1778
- funcNode.josi = 'して';
1803
+ funcNode.josi = (this.isReadingCalc && t.josi === 'には') ? '' : 'して';
1779
1804
  return funcNode;
1780
1805
  }
1781
1806
  // 続き
@@ -2411,7 +2436,7 @@ export class NakoParser extends NakoParserBase {
2411
2436
  return null;
2412
2437
  }
2413
2438
  // 助詞がある? つまり、関数呼び出しがある?
2414
- if (t.josi === '') {
2439
+ if (t.josi === '' && !this.canNextFuncTakeNoJosiArg()) {
2415
2440
  return t;
2416
2441
  } // 値だけの場合
2417
2442
  // 関数の呼び出しがあるなら、スタックに載せて関数読み出しを呼ぶ
@@ -2451,6 +2476,18 @@ export class NakoParser extends NakoParserBase {
2451
2476
  }
2452
2477
  return fCalc;
2453
2478
  }
2479
+ /** 次の関数が空助詞の値を引数として受け取れるか */
2480
+ canNextFuncTakeNoJosiArg() {
2481
+ if (!this.check('func')) {
2482
+ return false;
2483
+ }
2484
+ const func = this.peek();
2485
+ const josiList = func?.meta?.josi;
2486
+ if (!josiList) {
2487
+ return false;
2488
+ }
2489
+ return josiList.some((josi) => josi.indexOf('') >= 0);
2490
+ }
2454
2491
  /** @returns {Ast | null} */
2455
2492
  yValueKakko() {
2456
2493
  if (!this.check('(')) {
@@ -2604,7 +2641,7 @@ export class NakoParser extends NakoParserBase {
2604
2641
  }
2605
2642
  asyncFn = !!meta.asyncFn;
2606
2643
  }
2607
- return {
2644
+ const funcNode = {
2608
2645
  type: 'func',
2609
2646
  name: funcName,
2610
2647
  blocks: args,
@@ -2614,6 +2651,7 @@ export class NakoParser extends NakoParserBase {
2614
2651
  ...map,
2615
2652
  end: this.peekSourceMap()
2616
2653
  };
2654
+ return this.yApplyCallValue(funcNode);
2617
2655
  }
2618
2656
  throw NakoSyntaxError.fromNode('C風関数呼び出しのエラー', funcNameToken || NewEmptyToken());
2619
2657
  }
@@ -2788,7 +2826,7 @@ export class NakoParser extends NakoParserBase {
2788
2826
  const t = this.getCur();
2789
2827
  const word = this.getVarNameRef(t);
2790
2828
  // word[n] || word@n
2791
- if (word.josi === '' && this.checkTypes(['[', '@'])) {
2829
+ if ((word.josi === '' && this.checkTypes(['[', '@'])) || (word.josi !== '' && this.check('@'))) {
2792
2830
  const ast = {
2793
2831
  type: 'ref_array', // 配列参照
2794
2832
  name: word,
@@ -2808,7 +2846,7 @@ export class NakoParser extends NakoParserBase {
2808
2846
  return ast;
2809
2847
  }
2810
2848
  // オブジェクトプロパティ構文(参照) word$prop (#1793)
2811
- if (word.josi === '' && (this.check2(['$', 'word']) || this.check2(['$', 'string']))) {
2849
+ if (this.check2(['$', 'word']) || this.check2(['$', 'string'])) {
2812
2850
  const propList = [];
2813
2851
  let josi = '';
2814
2852
  while (this.check('$')) {
@@ -813,7 +813,7 @@ export class NakoParser extends NakoParserBase {
813
813
  return ans
814
814
  }
815
815
 
816
- yGetArgParen(y: Ast[]): Ast[] { // C言語風呼び出しでカッコの中を取得
816
+ yGetArgParen(y: Ast[], funcName?: string): Ast[] { // C言語風呼び出しでカッコの中を取得
817
817
  let isClose = false
818
818
  const si = this.stack.length
819
819
  while (!this.isEOF()) {
@@ -831,7 +831,8 @@ export class NakoParser extends NakoParserBase {
831
831
  break
832
832
  }
833
833
  if (!isClose) {
834
- throw NakoSyntaxError.fromNode(`C風関数『${(y[0] as AstStrValue).value}』でカッコが閉じていません`, y[0])
834
+ const name = funcName || (y[0] as AstStrValue).value || this.nodeToStr(y[0], { depth: 0, typeName: '関数' }, false)
835
+ throw NakoSyntaxError.fromNode(`C風関数『${name}』でカッコが閉じていません`, y[0])
835
836
  }
836
837
  const a: Ast[] = []
837
838
  while (si < this.stack.length) {
@@ -841,6 +842,31 @@ export class NakoParser extends NakoParserBase {
841
842
  return a
842
843
  }
843
844
 
845
+ /** 関数の戻り値を続けてC風呼び出しする */
846
+ yApplyCallValue(callee: Ast): Ast {
847
+ let node = callee
848
+ while (this.check('(')) {
849
+ this.get() // skip '('
850
+ const args = this.yGetArgParen([node], '関数呼び出しの結果')
851
+ if (!this.check(')')) {
852
+ throw NakoSyntaxError.fromNode('C風関数呼び出しのエラー', node)
853
+ }
854
+ const close = this.get()
855
+ node = {
856
+ type: 'call_value',
857
+ blocks: [node, ...args],
858
+ josi: close?.josi || '',
859
+ startOffset: node.startOffset,
860
+ endOffset: close?.endOffset,
861
+ line: node.line,
862
+ column: node.column,
863
+ file: node.file,
864
+ end: this.peekSourceMap()
865
+ } as AstBlocks
866
+ }
867
+ return node
868
+ }
869
+
844
870
  /** @returns {AstRepeatTimes | null} */
845
871
  yRepeatTime(): AstRepeatTimes | null {
846
872
  const map = this.peekSourceMap()
@@ -1542,7 +1568,7 @@ export class NakoParser extends NakoParserBase {
1542
1568
 
1543
1569
  // 「**して、**」の場合も一度切る
1544
1570
  if (RenbunJosi.indexOf(t.josi) >= 0) {
1545
- funcNode.josi = 'して'
1571
+ funcNode.josi = (this.isReadingCalc && t.josi === 'には') ? '' : 'して'
1546
1572
  return funcNode
1547
1573
  }
1548
1574
  // 続き
@@ -2139,7 +2165,7 @@ export class NakoParser extends NakoParserBase {
2139
2165
  const t = this.yGetArg()
2140
2166
  if (!t) { return null }
2141
2167
  // 助詞がある? つまり、関数呼び出しがある?
2142
- if (t.josi === '') { return t } // 値だけの場合
2168
+ if (t.josi === '' && !this.canNextFuncTakeNoJosiArg()) { return t } // 値だけの場合
2143
2169
  // 関数の呼び出しがあるなら、スタックに載せて関数読み出しを呼ぶ
2144
2170
  const tmpReadingCalc = this.isReadingCalc
2145
2171
  this.isReadingCalc = true
@@ -2176,6 +2202,15 @@ export class NakoParser extends NakoParserBase {
2176
2202
  return fCalc
2177
2203
  }
2178
2204
 
2205
+ /** 次の関数が空助詞の値を引数として受け取れるか */
2206
+ canNextFuncTakeNoJosiArg(): boolean {
2207
+ if (!this.check('func')) { return false }
2208
+ const func = this.peek() as TokenCallFunc | null
2209
+ const josiList = func?.meta?.josi
2210
+ if (!josiList) { return false }
2211
+ return josiList.some((josi) => josi.indexOf('') >= 0)
2212
+ }
2213
+
2179
2214
  /** @returns {Ast | null} */
2180
2215
  yValueKakko(): Ast | null {
2181
2216
  if (!this.check('(')) { return null }
@@ -2318,7 +2353,7 @@ export class NakoParser extends NakoParserBase {
2318
2353
  }
2319
2354
  asyncFn = !!meta.asyncFn
2320
2355
  }
2321
- return {
2356
+ const funcNode = {
2322
2357
  type: 'func',
2323
2358
  name: funcName,
2324
2359
  blocks: args,
@@ -2328,6 +2363,7 @@ export class NakoParser extends NakoParserBase {
2328
2363
  ...map,
2329
2364
  end: this.peekSourceMap()
2330
2365
  } as AstCallFunc
2366
+ return this.yApplyCallValue(funcNode)
2331
2367
  }
2332
2368
  throw NakoSyntaxError.fromNode('C風関数呼び出しのエラー', funcNameToken || NewEmptyToken())
2333
2369
  }
@@ -2497,7 +2533,7 @@ export class NakoParser extends NakoParserBase {
2497
2533
  const word = this.getVarNameRef(t)
2498
2534
 
2499
2535
  // word[n] || word@n
2500
- if (word.josi === '' && this.checkTypes(['[', '@'])) {
2536
+ if ((word.josi === '' && this.checkTypes(['[', '@'])) || (word.josi !== '' && this.check('@'))) {
2501
2537
  const ast: Ast = {
2502
2538
  type: 'ref_array', // 配列参照
2503
2539
  name: word,
@@ -2514,7 +2550,7 @@ export class NakoParser extends NakoParserBase {
2514
2550
  }
2515
2551
 
2516
2552
  // オブジェクトプロパティ構文(参照) word$prop (#1793)
2517
- if (word.josi === '' && (this.check2(['$', 'word']) || this.check2(['$', 'string']))) {
2553
+ if (this.check2(['$', 'word']) || this.check2(['$', 'string'])) {
2518
2554
  const propList: Ast[] = []
2519
2555
  let josi = ''
2520
2556
  while (this.check('$')) {
@@ -109,6 +109,22 @@ describe('array_test', async () => {
109
109
  await cmp('([{"犬": "わんわん"}])[0]$犬を表示', 'わんわん')
110
110
  await cmp('([{"犬": ["わんわん"]}])[0]$犬@0を表示', 'わんわん')
111
111
  })
112
+ it('$の前に助詞を書けるようにする #2313', async () => {
113
+ // 参照
114
+ await cmp('色見本={"赤": "#F00", "青": "#00F"};色見本の$赤を表示。', '#F00')
115
+ await cmp('色見本={"赤": "#F00", "青": "#00F"};色見本から$赤を表示。', '#F00')
116
+ // 代入
117
+ await cmp('色見本={"赤": "#F00", "青": "#00F"};色見本の$赤は「#FF0000」;色見本の$赤を表示。', '#FF0000')
118
+ await cmp('色見本={}; 色見本で$赤は「#F00」; 色見本で$赤を表示。', '#F00')
119
+ })
120
+ it('@の前に助詞を書けるようにする(参照) #2313', async () => {
121
+ await cmp('色見本={"赤": "#F00", "青": "#00F"};色見本の@"赤"を表示。', '#F00')
122
+ await cmp('色見本={"赤": "#F00", "青": "#00F"};色見本から@"赤"を表示。', '#F00')
123
+ })
124
+ it('@の前に助詞を書けるようにする(代入) #2313', async () => {
125
+ await cmp('色見本={"赤": "#F00", "青": "#00F"};色見本の@"赤"は「#FF0000」;色見本の@"赤"を表示。', '#FF0000')
126
+ await cmp('色見本={}; 色見本で@"赤"は「#F00」; 色見本で@"赤"を表示。', '#F00')
127
+ })
112
128
  it('配列+オブジェクトプロパティ #2139', async () => {
113
129
  await cmp('A=[{"b":10}]; A[0].b = 20; A[0].bを表示。', '20')
114
130
  })
@@ -58,6 +58,17 @@ describe('関数呼び出しテスト', async () => {
58
58
  it('**には**構文 - 配列カスタムソート', async () => {
59
59
  await cmp('A=[5,1,3];Aを配列カスタムソートするには(a,b);それはb-a;ここまで;Aを「:」で配列結合して表示', '5:3:1')
60
60
  })
61
+ it('代入文と**には**構文を組み合わせて戻り値を取得する #1994', async () => {
62
+ await cmp(`
63
+ TIDは0.001秒後には
64
+ 「時間だよ」を表示
65
+ ここまで
66
+ TIDのタイマー停止
67
+ もし(TID!=undefined)ならば
68
+ 「OK」を表示
69
+ ここまで
70
+ `, 'OK')
71
+ })
61
72
  it('階乗計算 - 再帰', async () => {
62
73
  await cmp('●(VをAのBで)階乗計算とは;' +
63
74
  'もし、Bが0以下ならば、Vを戻す。;(V*A)をAの(B-1)で階乗計算して戻す。' +