nadesiko3 3.6.28 → 3.6.31

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/core/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nadesiko3core",
3
- "version": "3.6.28",
3
+ "version": "3.6.31",
4
4
  "description": "Japanese Programming Language Nadesiko v3 core",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
@@ -983,17 +983,6 @@ export class NakoGen {
983
983
  }
984
984
  return code;
985
985
  }
986
- convRefProp(node) {
987
- const name = this._convGen(node.name, true);
988
- const list = node.index;
989
- if (!list || list.length <= 0) {
990
- throw NakoSyntaxError.fromNode('プロパティがありません。', node);
991
- }
992
- const prop = list[0].value;
993
- const code = `( (function(){ if (${name}.__getProp) { return ${name}.__getProp('${prop}', __self) } ` +
994
- `else { return ${name}['${prop}'] } })() )`;
995
- return code;
996
- }
997
986
  convLetArray(node) {
998
987
  const id = this.loopId++;
999
988
  const valueNode = node.blocks[0];
@@ -1771,15 +1760,13 @@ export class NakoGen {
1771
1760
  if (!node.blocks || node.blocks.length == 0) {
1772
1761
  throw NakoSyntaxError.fromNode('代入する値がありません。', node);
1773
1762
  }
1774
- const astProp = node.index[0];
1775
- const astValue = node.blocks[0];
1776
- let prop = '';
1777
- if (astProp) {
1778
- prop = astProp.value;
1779
- }
1780
- else {
1763
+ const propList = node.index;
1764
+ const propTopAst = propList.pop();
1765
+ if (propTopAst === undefined) {
1781
1766
  throw NakoSyntaxError.fromNode('代入する先のプロパティ名がありません。', node);
1782
1767
  }
1768
+ const propTop = (typeof propTopAst.value === 'string') ? propTopAst.value : '';
1769
+ const astValue = node.blocks[0];
1783
1770
  let value = null;
1784
1771
  // 値のプログラムを生成
1785
1772
  if (astValue) {
@@ -1796,12 +1783,56 @@ export class NakoGen {
1796
1783
  if (res === null) {
1797
1784
  throw NakoSyntaxError.fromNode(`変数『${name}』が見当たりません。`, node);
1798
1785
  }
1786
+ // 変数のプロパティ
1787
+ let nameJs = res.js;
1788
+ if (propList.length > 0) {
1789
+ for (const prop of propList) {
1790
+ if (typeof prop.value === 'string') {
1791
+ nameJs += `['${prop.value}']`;
1792
+ }
1793
+ else {
1794
+ throw NakoSyntaxError.fromNode(`変数『${nameJs}』以下のプロパティにアクセスできません。`, node);
1795
+ }
1796
+ }
1797
+ }
1799
1798
  // プロパティへの代入式を作る
1800
- const propVar = `${res.js}['${prop}']`;
1801
- code += `if (typeof ${res.js}.__setProp === 'function') { ${res.js}.__setProp('${prop}', ${value}, __self); } `;
1802
- code += `else { ${propVar} = ${value} };`;
1799
+ code += `if (typeof ${nameJs}.__setProp === 'function') { ${nameJs}.__setProp('${propTop}', ${value}, __self); } `;
1800
+ code += `else { ${nameJs}['${propTop}'] = ${value} };`;
1803
1801
  return ';' + this.convLineno(node, false) + code + '\n';
1804
1802
  }
1803
+ // プロパティへの参照 (#1793)
1804
+ convRefProp(node) {
1805
+ const name = this._convGen(node.name, true);
1806
+ const list = node.index;
1807
+ if (!list || list.length <= 0) {
1808
+ throw NakoSyntaxError.fromNode('プロパティがありません。', node);
1809
+ }
1810
+ let code;
1811
+ const propList = list;
1812
+ if (propList.length <= 1) {
1813
+ const propKey = propList[0].value;
1814
+ const code_call = `${name}.__getProp('${propKey}', __self)`;
1815
+ const code_prop = `${name}['${propKey}']`;
1816
+ const code_if = `if (${name}.__getProp) { return ${code_call} } else { return ${code_prop} }`;
1817
+ code = `( (()=>{ ${code_if} })() )`;
1818
+ }
1819
+ else {
1820
+ const arrs = [];
1821
+ const keys = [];
1822
+ for (let i = 0; i < propList.length; i++) {
1823
+ const propKey = propList[i].value;
1824
+ keys.push(`['${propKey}']`);
1825
+ arrs.push(`'${propKey}'`);
1826
+ }
1827
+ const keyStr = keys.join('');
1828
+ const arrStr = '[' + arrs.join(',') + ']';
1829
+ const code_call = `${name}.__getProp(${arrStr}, __self)`;
1830
+ const code_prop = `${name}${keyStr}`;
1831
+ const code_if = `if (${name}.__getProp) { return ${code_call} } else { return ${code_prop} }`;
1832
+ code = `( (()=>{ ${code_if} })() )`;
1833
+ }
1834
+ return code;
1835
+ }
1805
1836
  convDefLocalVar(node) {
1806
1837
  const astValue = node.blocks[0];
1807
1838
  let value = '0';
@@ -1059,19 +1059,6 @@ export class NakoGen {
1059
1059
  return code
1060
1060
  }
1061
1061
 
1062
- convRefProp (node: Ast): string {
1063
- const name = this._convGen(node.name as Ast, true)
1064
- const list: Ast[] | undefined = node.index
1065
- if (!list || list.length <= 0) {
1066
- throw NakoSyntaxError.fromNode('プロパティがありません。', node)
1067
- }
1068
- const prop = (list[0] as AstStrValue).value
1069
- const code =
1070
- `( (function(){ if (${name}.__getProp) { return ${name}.__getProp('${prop}', __self) } ` +
1071
- `else { return ${name}['${prop}'] } })() )`
1072
- return code
1073
- }
1074
-
1075
1062
  convLetArray (node: AstLetArray): string {
1076
1063
  const id = this.loopId++
1077
1064
  const valueNode: Ast = node.blocks[0]
@@ -1834,14 +1821,11 @@ export class NakoGen {
1834
1821
  convLetProp(node: AstLet): string {
1835
1822
  if (!node.index || node.index.length == 0) { throw NakoSyntaxError.fromNode('代入する先のプロパティ名がありません。', node) }
1836
1823
  if (!node.blocks || node.blocks.length == 0) { throw NakoSyntaxError.fromNode('代入する値がありません。', node) }
1837
- const astProp = node.index[0]
1824
+ const propList = node.index as AstStrValue[]
1825
+ const propTopAst = propList.pop()
1826
+ if (propTopAst === undefined) { throw NakoSyntaxError.fromNode('代入する先のプロパティ名がありません。', node) }
1827
+ const propTop = (typeof propTopAst.value === 'string') ? propTopAst.value : ''
1838
1828
  const astValue = node.blocks[0]
1839
- let prop = ''
1840
- if (astProp) {
1841
- prop = (astProp as AstStrValue).value
1842
- } else {
1843
- throw NakoSyntaxError.fromNode('代入する先のプロパティ名がありません。', node)
1844
- }
1845
1829
  let value = null
1846
1830
  // 値のプログラムを生成
1847
1831
  if (astValue) {
@@ -1849,7 +1833,6 @@ export class NakoGen {
1849
1833
  } else {
1850
1834
  throw NakoSyntaxError.fromNode('代入する値がありません。', node)
1851
1835
  }
1852
-
1853
1836
  // 変数名
1854
1837
  const name: string = node.name
1855
1838
  const res = this.findVar(name, value)
@@ -1858,12 +1841,55 @@ export class NakoGen {
1858
1841
  if (res === null) {
1859
1842
  throw NakoSyntaxError.fromNode(`変数『${name}』が見当たりません。`, node)
1860
1843
  }
1844
+ // 変数のプロパティ
1845
+ let nameJs = res.js
1846
+ if (propList.length > 0) {
1847
+ for (const prop of propList) {
1848
+ if (typeof prop.value === 'string') {
1849
+ nameJs += `['${prop.value}']`
1850
+ } else {
1851
+ throw NakoSyntaxError.fromNode(
1852
+ `変数『${nameJs}』以下のプロパティにアクセスできません。`, node)
1853
+ }
1854
+ }
1855
+ }
1861
1856
  // プロパティへの代入式を作る
1862
- const propVar = `${res.js}['${prop}']`
1863
- code += `if (typeof ${res.js}.__setProp === 'function') { ${res.js}.__setProp('${prop}', ${value}, __self); } `
1864
- code += `else { ${propVar} = ${value} };`
1857
+ code += `if (typeof ${nameJs}.__setProp === 'function') { ${nameJs}.__setProp('${propTop}', ${value}, __self); } `
1858
+ code += `else { ${nameJs}['${propTop}'] = ${value} };`
1865
1859
  return ';' + this.convLineno(node, false) + code + '\n'
1866
1860
  }
1861
+ // プロパティへの参照 (#1793)
1862
+ convRefProp(node: Ast): string {
1863
+ const name = this._convGen(node.name as Ast, true)
1864
+ const list: Ast[] | undefined = node.index
1865
+ if (!list || list.length <= 0) {
1866
+ throw NakoSyntaxError.fromNode('プロパティがありません。', node)
1867
+ }
1868
+ let code
1869
+ const propList = list as AstStrValue[]
1870
+ if (propList.length <= 1) {
1871
+ const propKey = propList[0].value
1872
+ const code_call = `${name}.__getProp('${propKey}', __self)`
1873
+ const code_prop = `${name}['${propKey}']`
1874
+ const code_if = `if (${name}.__getProp) { return ${code_call} } else { return ${code_prop} }`
1875
+ code = `( (()=>{ ${code_if} })() )`
1876
+ } else {
1877
+ const arrs = []
1878
+ const keys = []
1879
+ for (let i = 0; i < propList.length; i++) {
1880
+ const propKey = propList[i].value
1881
+ keys.push(`['${propKey}']`)
1882
+ arrs.push(`'${propKey}'`)
1883
+ }
1884
+ const keyStr = keys.join('')
1885
+ const arrStr = '[' + arrs.join(',') + ']'
1886
+ const code_call = `${name}.__getProp(${arrStr}, __self)`
1887
+ const code_prop = `${name}${keyStr}`
1888
+ const code_if = `if (${name}.__getProp) { return ${code_call} } else { return ${code_prop} }`
1889
+ code = `( (()=>{ ${code_if} })() )`
1890
+ }
1891
+ return code
1892
+ }
1867
1893
 
1868
1894
  convDefLocalVar(node: AstDefVar): string {
1869
1895
  const astValue = node.blocks[0]
@@ -11,7 +11,10 @@ const wordSpecial = /^(かつ|または)/;
11
11
  const errorRead = (ch) => {
12
12
  return function () { throw new Error('突然の『' + ch + '』があります。'); };
13
13
  };
14
- export const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|px|dot|pt|em|b|mb|kb|gb)/;
14
+ // 数値の後の単位は自動的に省略されるルール (#994)
15
+ export const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|b|mb|kb|gb)/;
16
+ // CSSの単位であれば自動的に文字列に変換するルール (#1811)
17
+ export const cssUnitRE = /^(px|em|ex|rem|vw|vh|vmin|vmax)/;
15
18
  /** トークンに区切るルールの一覧 */
16
19
  export const rules = [
17
20
  // 上から順にマッチさせていく
@@ -81,7 +84,7 @@ export const rules = [
81
84
  { name: ')', pattern: /^\)/, readJosi: true },
82
85
  { name: '|', pattern: /^\|/ },
83
86
  { name: '??', pattern: /^\?\?/ }, // 「表示」のエイリアス #1745
84
- { name: '$', pattern: /^\$/ }, // #1793 プロパティアクセス
87
+ { name: '$', pattern: /^(\$|\.)/ }, // プロパティアクセス (#1793)(#1807)
85
88
  { name: 'string', pattern: /^🌿/, cbParser: src => cbString('🌿', '🌿', src) },
86
89
  { name: 'string_ex', pattern: /^🌴/, cbParser: src => cbString('🌴', '🌴', src) },
87
90
  { name: 'string_ex', pattern: /^「/, cbParser: src => cbString('「', '」', src) },
@@ -15,7 +15,10 @@ const errorRead = (ch: string): any => {
15
15
  return function () { throw new Error('突然の『' + ch + '』があります。') }
16
16
  }
17
17
 
18
- export const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|px|dot|pt|em|b|mb|kb|gb)/
18
+ // 数値の後の単位は自動的に省略されるルール (#994)
19
+ export const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|b|mb|kb|gb)/
20
+ // CSSの単位であれば自動的に文字列に変換するルール (#1811)
21
+ export const cssUnitRE = /^(px|em|ex|rem|vw|vh|vmin|vmax)/
19
22
 
20
23
  export interface NakoLexParseResult {
21
24
  src: string;
@@ -101,7 +104,7 @@ export const rules: NakoLexRule[] = [
101
104
  { name: ')', pattern: /^\)/, readJosi: true },
102
105
  { name: '|', pattern: /^\|/ },
103
106
  { name: '??', pattern: /^\?\?/ }, // 「表示」のエイリアス #1745
104
- { name: '$', pattern: /^\$/ }, // #1793 プロパティアクセス
107
+ { name: '$', pattern: /^(\$|\.)/ }, // プロパティアクセス (#1793)(#1807)
105
108
  { name: 'string', pattern: /^🌿/, cbParser: src => cbString('🌿', '🌿', src) },
106
109
  { name: 'string_ex', pattern: /^🌴/, cbParser: src => cbString('🌴', '🌴', src) },
107
110
  { name: 'string_ex', pattern: /^「/, cbParser: src => cbString('「', '」', src) },
@@ -10,7 +10,7 @@ import { isIndentChars } from './nako_indent_chars.mjs';
10
10
  // 助詞の一覧
11
11
  import { josiRE, removeJosiMap, tararebaMap, josiListExport } from './nako_josi_list.mjs';
12
12
  // 字句解析ルールの一覧
13
- import { rules, unitRE } from './nako_lex_rules.mjs';
13
+ import { rules, unitRE, cssUnitRE } from './nako_lex_rules.mjs';
14
14
  import { NakoLexerError, InternalLexerError } from './nako_errors.mjs';
15
15
  export class NakoLexer {
16
16
  /**
@@ -578,6 +578,7 @@ export class NakoLexer {
578
578
  if (!m) {
579
579
  continue;
580
580
  }
581
+ let ruleName = rule.name;
581
582
  ok = true;
582
583
  // 空白ならスキップ
583
584
  if (rule.name === 'space') {
@@ -676,6 +677,14 @@ export class NakoLexer {
676
677
  src = src.substring(um[0].length);
677
678
  column += m[0].length;
678
679
  }
680
+ // CSSの単位なら自動的に文字列として認識させる #1811
681
+ const cssUnit = cssUnitRE.exec(src);
682
+ if (cssUnit) {
683
+ ruleName = 'string';
684
+ src = src.substring(cssUnit[0].length);
685
+ column += m[0].length;
686
+ value += cssUnit[0];
687
+ }
679
688
  }
680
689
  let josi = '';
681
690
  if (rule.readJosi) {
@@ -699,7 +708,7 @@ export class NakoLexer {
699
708
  }
700
709
  }
701
710
  }
702
- switch (rule.name) {
711
+ switch (ruleName) {
703
712
  case 'def_test': {
704
713
  isDefTest = true;
705
714
  break;
@@ -713,12 +722,12 @@ export class NakoLexer {
713
722
  }
714
723
  }
715
724
  // ここまで‰(#682) を処理
716
- if (rule.name === 'dec_lineno') {
725
+ if (ruleName === 'dec_lineno') {
717
726
  line--;
718
727
  continue;
719
728
  }
720
729
  result.push({
721
- type: rule.name,
730
+ type: ruleName,
722
731
  value,
723
732
  indent,
724
733
  line: lineCurrent,
@@ -729,7 +738,7 @@ export class NakoLexer {
729
738
  preprocessedCodeLength: (srcLength - src.length) - srcOffset
730
739
  });
731
740
  // 改行のとき次の行のインデントを調べる。なお、改行の後は必ずcolumnが1になる。インデント構文のため、一行に2つ以上の文を含むときを考慮する。(core #66)
732
- if (rule.name === 'eol' && column === 1) {
741
+ if (ruleName === 'eol' && column === 1) {
733
742
  const ia = this.countIndent(src);
734
743
  indent = ia[0];
735
744
  column += ia[1];
@@ -14,7 +14,7 @@ import { isIndentChars } from './nako_indent_chars.mjs'
14
14
  import { josiRE, removeJosiMap, tararebaMap, josiListExport } from './nako_josi_list.mjs'
15
15
 
16
16
  // 字句解析ルールの一覧
17
- import { rules, unitRE, NakoLexParseResult } from './nako_lex_rules.mjs'
17
+ import { rules, unitRE, cssUnitRE, NakoLexParseResult } from './nako_lex_rules.mjs'
18
18
  import { NakoLexerError, InternalLexerError } from './nako_errors.mjs'
19
19
 
20
20
  import { FuncList, FuncArgs, ExportMap, FuncListItem } from './nako_types.mjs'
@@ -563,6 +563,7 @@ export class NakoLexer {
563
563
  // 正規表現でマッチ
564
564
  const m = rule.pattern.exec(src)
565
565
  if (!m) { continue }
566
+ let ruleName = rule.name
566
567
  ok = true
567
568
  // 空白ならスキップ
568
569
  if (rule.name === 'space') {
@@ -673,6 +674,14 @@ export class NakoLexer {
673
674
  src = src.substring(um[0].length)
674
675
  column += m[0].length
675
676
  }
677
+ // CSSの単位なら自動的に文字列として認識させる #1811
678
+ const cssUnit = cssUnitRE.exec(src)
679
+ if (cssUnit) {
680
+ ruleName = 'string'
681
+ src = src.substring(cssUnit[0].length)
682
+ column += m[0].length
683
+ value += cssUnit[0]
684
+ }
676
685
  }
677
686
 
678
687
  let josi = ''
@@ -696,7 +705,7 @@ export class NakoLexer {
696
705
  }
697
706
  }
698
707
 
699
- switch (rule.name) {
708
+ switch (ruleName) {
700
709
  case 'def_test': {
701
710
  isDefTest = true
702
711
  break
@@ -710,13 +719,13 @@ export class NakoLexer {
710
719
  }
711
720
  }
712
721
  // ここまで‰(#682) を処理
713
- if (rule.name === 'dec_lineno') {
722
+ if (ruleName === 'dec_lineno') {
714
723
  line--
715
724
  continue
716
725
  }
717
726
 
718
727
  result.push({
719
- type: rule.name,
728
+ type: ruleName,
720
729
  value,
721
730
  indent,
722
731
  line: lineCurrent,
@@ -727,7 +736,7 @@ export class NakoLexer {
727
736
  preprocessedCodeLength: (srcLength - src.length) - srcOffset
728
737
  })
729
738
  // 改行のとき次の行のインデントを調べる。なお、改行の後は必ずcolumnが1になる。インデント構文のため、一行に2つ以上の文を含むときを考慮する。(core #66)
730
- if (rule.name === 'eol' && column === 1) {
739
+ if (ruleName === 'eol' && column === 1) {
731
740
  const ia = this.countIndent(src)
732
741
  indent = ia[0]
733
742
  column += ia[1]
@@ -1789,25 +1789,34 @@ export class NakoParser extends NakoParserBase {
1789
1789
  throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, map);
1790
1790
  }
1791
1791
  }
1792
- // プロパティ代入文 (#1793)
1793
- if (this.check2(['word', '$', 'word', 'eq']) || this.check2(['word', '$', 'string', 'eq'])) {
1794
- const word = this.peek();
1795
- if (this.accept(['word', '$', 'word', 'eq', this.yCalc]) || this.accept(['word', '$', 'string', 'eq', this.yCalc])) {
1796
- const nameToken = this.getVarName(this.y[0]);
1797
- const propToken = this.y[2];
1798
- const valueToken = this.y[4];
1799
- return {
1800
- type: 'let_prop',
1801
- name: nameToken.value,
1802
- index: [propToken],
1803
- blocks: [valueToken],
1804
- josi: '',
1805
- ...map,
1806
- end: this.peekSourceMap()
1807
- };
1792
+ // オブジェクトプロパティ構文 代入文 (#1793)
1793
+ if (this.check2(['word', '$', '*', '$', '*', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', 'eq'])) {
1794
+ const propList = [];
1795
+ const word = this.getVarName(this.get());
1796
+ for (;;) {
1797
+ const flag = this.peek();
1798
+ if (flag === null || flag.type !== '$') {
1799
+ break;
1800
+ }
1801
+ this.get(); // skip $
1802
+ propList.push(this.get()); // property
1808
1803
  }
1809
- throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文の計算式に書き間違いがあります。`, map);
1804
+ this.get(); // skip eq
1805
+ const valueToken = this.yCalc(); // calc
1806
+ if (valueToken === null) {
1807
+ throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文の計算式に書き間違いがあります。`, map);
1808
+ }
1809
+ return {
1810
+ type: 'let_prop',
1811
+ name: word.value,
1812
+ index: propList,
1813
+ blocks: [valueToken],
1814
+ josi: '',
1815
+ ...map,
1816
+ end: this.peekSourceMap()
1817
+ };
1810
1818
  }
1819
+ // オブジェクトプロパティ構文 ここまで
1811
1820
  // let_array ?
1812
1821
  if (this.check2(['word', '@'])) {
1813
1822
  const la = this.yLetArrayAt(map);
@@ -2600,15 +2609,21 @@ export class NakoParser extends NakoParserBase {
2600
2609
  }
2601
2610
  return ast;
2602
2611
  }
2603
- // word$prop
2612
+ // オブジェクトプロパティ構文(参照) word$prop (#1793)
2604
2613
  if (word.josi === '' && (this.check2(['$', 'word']) || this.check2(['$', 'string']))) {
2605
- this.get(); // skip '$'
2606
- const prop = this.get();
2614
+ const propList = [];
2615
+ let josi = '';
2616
+ while (this.check('$')) {
2617
+ this.get(); // skip '$'
2618
+ const prop = this.get();
2619
+ propList.push(prop);
2620
+ josi = prop.josi;
2621
+ }
2607
2622
  return {
2608
2623
  type: 'ref_prop', // プロパティ参照
2609
2624
  name: word,
2610
- index: [prop],
2611
- josi: prop.josi,
2625
+ index: propList,
2626
+ josi: josi,
2612
2627
  ...map,
2613
2628
  end: this.peekSourceMap()
2614
2629
  };
@@ -1556,25 +1556,33 @@ export class NakoParser extends NakoParserBase {
1556
1556
  throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, map)
1557
1557
  }
1558
1558
  }
1559
- // プロパティ代入文 (#1793)
1560
- if (this.check2(['word', '$', 'word', 'eq']) || this.check2(['word', '$', 'string', 'eq'])) {
1561
- const word = this.peek()
1562
- if (this.accept(['word', '$', 'word', 'eq', this.yCalc]) || this.accept(['word', '$', 'string', 'eq', this.yCalc])) {
1563
- const nameToken = this.getVarName(this.y[0])
1564
- const propToken = this.y[2]
1565
- const valueToken = this.y[4]
1566
- return {
1567
- type: 'let_prop',
1568
- name: (nameToken as AstStrValue).value,
1569
- index: [propToken],
1570
- blocks: [valueToken],
1571
- josi: '',
1572
- ...map,
1573
- end: this.peekSourceMap()
1574
- } as AstLet
1559
+
1560
+ // オブジェクトプロパティ構文 代入文 (#1793)
1561
+ if (this.check2(['word', '$', '*', '$', '*', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', 'eq'])) {
1562
+ const propList = []
1563
+ const word = this.getVarName(this.get() as Token)
1564
+ for (;;) {
1565
+ const flag = this.peek()
1566
+ if (flag === null || flag.type !== '$') { break }
1567
+ this.get() // skip $
1568
+ propList.push(this.get() as Ast) // property
1575
1569
  }
1576
- throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文の計算式に書き間違いがあります。`, map)
1570
+ this.get() // skip eq
1571
+ const valueToken = this.yCalc() // calc
1572
+ if (valueToken === null) {
1573
+ throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文の計算式に書き間違いがあります。`, map)
1574
+ }
1575
+ return {
1576
+ type: 'let_prop',
1577
+ name: (word as AstStrValue).value,
1578
+ index: propList,
1579
+ blocks: [valueToken],
1580
+ josi: '',
1581
+ ...map,
1582
+ end: this.peekSourceMap()
1583
+ } as AstLet
1577
1584
  }
1585
+ // オブジェクトプロパティ構文 ここまで
1578
1586
 
1579
1587
  // let_array ?
1580
1588
  if (this.check2(['word', '@'])) {
@@ -2311,15 +2319,21 @@ export class NakoParser extends NakoParserBase {
2311
2319
  return ast
2312
2320
  }
2313
2321
 
2314
- // word$prop
2322
+ // オブジェクトプロパティ構文(参照) word$prop (#1793)
2315
2323
  if (word.josi === '' && (this.check2(['$', 'word']) || this.check2(['$', 'string']))) {
2316
- this.get() // skip '$'
2317
- const prop = this.get() as Token
2324
+ const propList: Ast[] = []
2325
+ let josi = ''
2326
+ while (this.check('$')) {
2327
+ this.get() // skip '$'
2328
+ const prop = this.get() as Token
2329
+ propList.push(prop as Ast)
2330
+ josi = prop.josi
2331
+ }
2318
2332
  return {
2319
2333
  type: 'ref_prop', // プロパティ参照
2320
2334
  name: word,
2321
- index: [prop as Ast],
2322
- josi: prop.josi,
2335
+ index: propList,
2336
+ josi: josi,
2323
2337
  ...map,
2324
2338
  end: this.peekSourceMap()
2325
2339
  }
@@ -376,6 +376,10 @@ describe('basic', async () => {
376
376
  await cmp('A={"幅":30};A$幅=50;A$幅を表示', '50')
377
377
  await cmp('A={"高":30};A$高さ=50;A$高さを表示', '50') // 送り仮名の省略
378
378
  })
379
+ it('オブジェクトを手軽に設定する-ドットアクセス(#1807)', async () => {
380
+ await cmp('A={"高":30};A.高=50;A.高を表示', '50') //
381
+ await cmp('A={"A":30,"B":50};A.A=500;A.Aを表示', '500') //
382
+ })
379
383
  it('オブジェクトを手軽に設定する-プロパティ関数(#1793)', async () => {
380
384
  // プロパティの値を取得して10倍にして返す
381
385
  await cmp(
@@ -394,4 +398,13 @@ describe('basic', async () => {
394
398
  await cmp('A={"幅":30};A$"幅"=50;A$"幅"を表示', '50')
395
399
  await cmp('A={"高":30};A$"高"=50;A$"高"を表示', '50') // 送り仮名の省略
396
400
  })
401
+ it('オブジェクトプロパティ構文$でネスト可能にする #1805', async () => {
402
+ await cmp('A={"スタイル":{"幅":300}};A$スタイル$幅=1;A$スタイル$幅を表示', '1')
403
+ await cmp('A={"猫":{"三毛猫":1, "日本猫":2}};A$猫$日本猫=123;A$猫$日本猫を表示', '123')
404
+ })
405
+ it('CSSの単位付き数値を文字列として認識させる #1811', async () => {
406
+ await cmp('(TYPEOF(30px))を表示', 'string')
407
+ await cmp('A=30em;Aを表示', '30em')
408
+ await cmp('A=30px;AをJSONエンコードして表示', '"30px"')
409
+ })
397
410
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nadesiko3",
3
- "version": "3.6.28",
3
+ "version": "3.6.31",
4
4
  "description": "Japanese Programming Language",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
package/release/_hash.txt CHANGED
@@ -144,22 +144,22 @@ wnako3webworker.js:
144
144
  sha256(base64): zkHQ1vYOjoH4nffi+OulPeq/tql28XhdNuKpcaBCOKk=
145
145
  sha512(base64): cY0Mzrpoz/uEyY1n8MeJeWdDmm9vqI7dh28wEqJGtVRfYOJvDHiYDzzlfGndOWmzZmHUK6ZAE7YaUJ7zZxiZLw==
146
146
  version.js:
147
- md5(hex): 829792071c675c496dfbd9a1b4789806
148
- sha256(hex): ee04e83ef7c19f4a94f2bf53417e2e1131ab001dd767d984b1b0a0e7429b3fa9
149
- sha256(base64): 7gToPvfBn0qU8r9TQX4uETGrAB3XZ9mEsbCg50KbP6k=
150
- sha512(base64): u9FFKDXF26XCOVfT9Mh80T/aXrFQctrNIeRYxRqv6C0JIQ8lLm/Gd7PkvFmV2/WV224G9iBAJJr3t8+cdzNS5A==
147
+ md5(hex): f1362234962d1c50afbf9ce0aa23b7bc
148
+ sha256(hex): 9f7edac2f464dafd23f9cb9987936dd49c3c8c03429ac68da7493600d1c75f37
149
+ sha256(base64): n37awvRk2v0j+cuZh5Nt1Jw8jANCmsaNp0k2ANHHXzc=
150
+ sha512(base64): 0pMDIH7fqnXdu8+pGcl0z02O5rAzDFNzi2/j5E3N9kVMiNjwysNoaNzDVMTn7aBWF3c7JG1Pfb6Cz3aibNsp6Q==
151
151
  version_main.js:
152
- md5(hex): 829792071c675c496dfbd9a1b4789806
153
- sha256(hex): ee04e83ef7c19f4a94f2bf53417e2e1131ab001dd767d984b1b0a0e7429b3fa9
154
- sha256(base64): 7gToPvfBn0qU8r9TQX4uETGrAB3XZ9mEsbCg50KbP6k=
155
- sha512(base64): u9FFKDXF26XCOVfT9Mh80T/aXrFQctrNIeRYxRqv6C0JIQ8lLm/Gd7PkvFmV2/WV224G9iBAJJr3t8+cdzNS5A==
152
+ md5(hex): f1362234962d1c50afbf9ce0aa23b7bc
153
+ sha256(hex): 9f7edac2f464dafd23f9cb9987936dd49c3c8c03429ac68da7493600d1c75f37
154
+ sha256(base64): n37awvRk2v0j+cuZh5Nt1Jw8jANCmsaNp0k2ANHHXzc=
155
+ sha512(base64): 0pMDIH7fqnXdu8+pGcl0z02O5rAzDFNzi2/j5E3N9kVMiNjwysNoaNzDVMTn7aBWF3c7JG1Pfb6Cz3aibNsp6Q==
156
156
  wnako3.js:
157
- md5(hex): 50f7a896ca27b00945f556861dd61dd1
158
- sha256(hex): 28ba1d75eb5e5678a55444d581d0dfa489c8c926f0cd56f8d85dbf8771889fe1
159
- sha256(base64): KLoddeteVnilVETVgdDfpInIySbwzVb42F2/h3GIn+E=
160
- sha512(base64): V2TnS8xRh7eqbHoLzi6mP3MYwY48y7AUd2Ka+sv89BuWkr1nk1LF4vCEnQhYcQ32qclQlfRSAyYrmB0a9Nvz7g==
157
+ md5(hex): 2692c3fd365c8a3ff9a9d206af81000d
158
+ sha256(hex): 6324461eda1bbb9c5a4c7b6e5df90908f5e76a4cfdd14c83dc751e2534b0a9e3
159
+ sha256(base64): YyRGHtobu5xaTHtuXfkJCPXnakz90UyD3HUeJTSwqeM=
160
+ sha512(base64): r3o8nwLwABlWtdoD7i/tH/mzlYMw6fGDrSa6I/OYtilRaDnU/SpeROmVLwN+O5bEXAsdr+r6o5DQyafwG7HVrA==
161
161
  wnako3webworker.js:
162
- md5(hex): ba03e9c7ff7c56d0535bb5627466937e
163
- sha256(hex): fda46285e6457006b547147032863266a53fa74fed4c1495cda9bdbfbc898f30
164
- sha256(base64): /aRiheZFcAa1RxRwMoYyZqU/p0/tTBSVzam9v7yJjzA=
165
- sha512(base64): Hrhj1ySMaif/dpSxgw81MFETy5CiTsPQDgVUodAKWAdulumewUTBcP1yvkHzVwAho9fWWEqHrv1M0YY7IWpc2Q==
162
+ md5(hex): a7293f17a0350d541c40e563c69f71a1
163
+ sha256(hex): 38cbb6088ee124c3b36b20db3c1b8d7c4cd65b2b7914483817c63e150305b8fe
164
+ sha256(base64): OMu2CI7hJMOzayDbPBuNfEzWWyt5FEg4F8Y+FQMFuP4=
165
+ sha512(base64): 6WCJv1QD9Q0zjq6r46XBxw+YAkJGID0G8kY8RgP3OZF5WD5s4aDnomf6UqSUi9HL0vdtpi1WfzruoKdo3MSVPg==