starlight-cli 1.0.18 → 1.0.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/dist/index.js CHANGED
@@ -1457,9 +1457,20 @@ class Evaluator {
1457
1457
  return result;
1458
1458
  }
1459
1459
 
1460
- evalTemplateLiteral(node, env) {
1461
- return node.value;
1460
+ evalTemplateLiteral(node, env) {
1461
+ // node.parts is an array from parser: Literal or expression nodes
1462
+ let result = '';
1463
+ for (const part of node.parts) {
1464
+ if (part.type === 'Literal') {
1465
+ result += part.value;
1466
+ } else {
1467
+ const val = this.evaluate(part, env);
1468
+ result += val != null ? val.toString() : '';
1469
+ }
1462
1470
  }
1471
+ return result;
1472
+ }
1473
+
1463
1474
 
1464
1475
  evalImport(node, env) {
1465
1476
  const spec = node.path;
@@ -1730,7 +1741,7 @@ class Lexer {
1730
1741
  skipComment() {
1731
1742
  if (this.currentChar === '#') {
1732
1743
  if (this.peek() === '*') {
1733
- this.advance(); this.advance(); // skip #*
1744
+ this.advance(); this.advance(); // #*
1734
1745
  while (this.currentChar !== null) {
1735
1746
  if (this.currentChar === '*' && this.peek() === '#') {
1736
1747
  this.advance(); this.advance();
@@ -1738,7 +1749,7 @@ class Lexer {
1738
1749
  }
1739
1750
  this.advance();
1740
1751
  }
1741
- this.error("Unterminated multi-line comment (#* ... *#)");
1752
+ this.error('Unterminated multi-line comment (#* ... *#)');
1742
1753
  } else {
1743
1754
  while (this.currentChar && this.currentChar !== '\n') this.advance();
1744
1755
  }
@@ -1753,7 +1764,8 @@ class Lexer {
1753
1764
  }
1754
1765
 
1755
1766
  if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
1756
- result += '.'; this.advance();
1767
+ result += '.';
1768
+ this.advance();
1757
1769
  while (this.currentChar && /[0-9]/.test(this.currentChar)) {
1758
1770
  result += this.currentChar;
1759
1771
  this.advance();
@@ -1761,11 +1773,13 @@ class Lexer {
1761
1773
  }
1762
1774
 
1763
1775
  if (this.currentChar && /[eE]/.test(this.currentChar)) {
1764
- result += this.currentChar; this.advance();
1776
+ result += this.currentChar;
1777
+ this.advance();
1765
1778
  if (this.currentChar === '+' || this.currentChar === '-') {
1766
- result += this.currentChar; this.advance();
1779
+ result += this.currentChar;
1780
+ this.advance();
1767
1781
  }
1768
- if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
1782
+ if (!/[0-9]/.test(this.currentChar)) this.error('Invalid exponent in number');
1769
1783
  while (this.currentChar && /[0-9]/.test(this.currentChar)) {
1770
1784
  result += this.currentChar;
1771
1785
  this.advance();
@@ -1784,8 +1798,9 @@ class Lexer {
1784
1798
 
1785
1799
  const keywords = [
1786
1800
  'let', 'sldeploy', 'if', 'else', 'while', 'for',
1787
- 'break', 'continue', 'func', 'return', 'true', 'false', 'null',
1788
- 'ask', 'define', 'import', 'from', 'as', 'undefined'
1801
+ 'break', 'continue', 'func', 'return',
1802
+ 'true', 'false', 'null', 'undefined',
1803
+ 'ask', 'define', 'import', 'from', 'as'
1789
1804
  ];
1790
1805
 
1791
1806
  if (keywords.includes(result)) {
@@ -1803,59 +1818,58 @@ class Lexer {
1803
1818
  while (this.currentChar && this.currentChar !== quote) {
1804
1819
  if (this.currentChar === '\\') {
1805
1820
  this.advance();
1806
- switch (this.currentChar) {
1807
- case 'n': result += '\n'; break;
1808
- case 't': result += '\t'; break;
1809
- case '"': result += '"'; break;
1810
- case "'": result += "'"; break;
1811
- case '\\': result += '\\'; break;
1812
- default: result += this.currentChar;
1813
- }
1821
+ const map = { n: '\n', t: '\t', '"': '"', "'": "'", '\\': '\\' };
1822
+ result += map[this.currentChar] ?? this.currentChar;
1814
1823
  } else {
1815
1824
  result += this.currentChar;
1816
1825
  }
1817
1826
  this.advance();
1818
1827
  }
1819
1828
 
1820
- if (this.currentChar !== quote) {
1821
- this.error('Unterminated string literal');
1822
- }
1829
+ if (this.currentChar !== quote) this.error('Unterminated string literal');
1823
1830
 
1824
1831
  this.advance();
1825
1832
  return { type: 'STRING', value: result };
1826
1833
  }
1827
1834
 
1828
- templateString() {
1829
- this.advance();
1830
- let result = '';
1831
- while (this.currentChar && this.currentChar !== '`') {
1835
+
1836
+ templateString(tokens) {
1837
+ this.advance(); // skip opening `
1838
+
1839
+ let buffer = '';
1840
+
1841
+ while (this.currentChar !== null) {
1842
+ if (this.currentChar === '`') {
1843
+ if (buffer.length > 0) {
1844
+ tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
1845
+ }
1846
+ this.advance();
1847
+ return;
1848
+ }
1849
+
1832
1850
  if (this.currentChar === '$' && this.peek() === '{') {
1833
- this.advance(); this.advance(); // skip ${
1834
- result += '${'; // we leave the interpolation as-is for parser
1835
- continue;
1851
+ if (buffer.length > 0) {
1852
+ tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
1853
+ buffer = '';
1854
+ }
1855
+ this.advance(); // $
1856
+ this.advance(); // {
1857
+ tokens.push({ type: 'DOLLAR_LBRACE' });
1858
+ return;
1836
1859
  }
1837
1860
 
1838
1861
  if (this.currentChar === '\\') {
1839
1862
  this.advance();
1840
- switch (this.currentChar) {
1841
- case 'n': result += '\n'; break;
1842
- case 't': result += '\t'; break;
1843
- case '`': result += '`'; break;
1844
- case '\\': result += '\\'; break;
1845
- default: result += this.currentChar;
1846
- }
1847
- } else {
1848
- result += this.currentChar;
1863
+ buffer += this.currentChar;
1864
+ this.advance();
1865
+ continue;
1849
1866
  }
1850
- this.advance();
1851
- }
1852
1867
 
1853
- if (this.currentChar !== '`') {
1854
- this.error('Unterminated template string');
1868
+ buffer += this.currentChar;
1869
+ this.advance();
1855
1870
  }
1856
1871
 
1857
- this.advance();
1858
- return { type: 'TEMPLATE_STRING', value: result };
1872
+ this.error('Unterminated template string');
1859
1873
  }
1860
1874
 
1861
1875
  getTokens() {
@@ -1867,7 +1881,11 @@ class Lexer {
1867
1881
  if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
1868
1882
  if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
1869
1883
  if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
1870
- if (this.currentChar === '`') { tokens.push(this.templateString()); continue; }
1884
+
1885
+ if (this.currentChar === '`') {
1886
+ this.templateString(tokens);
1887
+ continue;
1888
+ }
1871
1889
 
1872
1890
  const char = this.currentChar;
1873
1891
  const next = this.peek();
@@ -1887,20 +1905,30 @@ class Lexer {
1887
1905
  if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
1888
1906
  if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
1889
1907
 
1890
- const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
1891
- if (next === '=' && map[char]) { tokens.push({ type: map[char] }); this.advance(); this.advance(); continue; }
1908
+ const compound = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
1909
+ if (next === '=' && compound[char]) {
1910
+ tokens.push({ type: compound[char] });
1911
+ this.advance(); this.advance();
1912
+ continue;
1913
+ }
1892
1914
 
1893
1915
  const singles = {
1894
1916
  '+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
1895
1917
  '=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
1896
- '(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
1897
- ';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
1918
+ '(': 'LPAREN', ')': 'RPAREN',
1919
+ '{': 'LBRACE', '}': 'RBRACE',
1920
+ '[': 'LBRACKET', ']': 'RBRACKET',
1921
+ ';': 'SEMICOLON', ',': 'COMMA',
1898
1922
  ':': 'COLON', '.': 'DOT'
1899
1923
  };
1900
1924
 
1901
- if (singles[char]) { tokens.push({ type: singles[char] }); this.advance(); continue; }
1925
+ if (singles[char]) {
1926
+ tokens.push({ type: singles[char] });
1927
+ this.advance();
1928
+ continue;
1929
+ }
1902
1930
 
1903
- this.error("Unexpected character: " + char);
1931
+ this.error(`Unexpected character: ${char}`);
1904
1932
  }
1905
1933
 
1906
1934
  tokens.push({ type: 'EOF' });
@@ -2142,6 +2170,23 @@ class Parser {
2142
2170
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
2143
2171
  return { type: 'ExpressionStatement', expression: expr };
2144
2172
  }
2173
+ parseTemplateLiteral() {
2174
+ const parts = [];
2175
+ while (true) {
2176
+ if (this.current.type === 'TEMPLATE_STRING') {
2177
+ parts.push({ type: 'Literal', value: this.current.value });
2178
+ this.eat('TEMPLATE_STRING');
2179
+ } else if (this.current.type === 'DOLLAR_LBRACE') {
2180
+ this.eat('DOLLAR_LBRACE');
2181
+ const expr = this.expression();
2182
+ parts.push(expr);
2183
+ this.eat('RBRACE');
2184
+ } else {
2185
+ break;
2186
+ }
2187
+ }
2188
+ return { type: 'TemplateLiteral', parts };
2189
+ }
2145
2190
 
2146
2191
  expression() {
2147
2192
  return this.assignment();
@@ -2289,7 +2334,11 @@ class Parser {
2289
2334
 
2290
2335
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
2291
2336
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
2292
- if (t.type === 'TEMPLATE_STRING') { this.eat('TEMPLATE_STRING'); return { type: 'TemplateLiteral', value: t.value }; }
2337
+ if (t.type === 'TEMPLATE_STRING') {
2338
+ return this.parseTemplateLiteral();
2339
+ }
2340
+
2341
+
2293
2342
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
2294
2343
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
2295
2344
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
@@ -2461,7 +2510,7 @@ const Lexer = __nccwpck_require__(211);
2461
2510
  const Parser = __nccwpck_require__(222);
2462
2511
  const Evaluator = __nccwpck_require__(112);
2463
2512
 
2464
- const VERSION = '1.0.18';
2513
+ const VERSION = '1.0.20';
2465
2514
 
2466
2515
  const COLOR = {
2467
2516
  reset: '\x1b[0m',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "starlight-cli",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "Starlight Programming Language CLI",
5
5
  "bin": {
6
6
  "starlight": "index.js"
package/src/evaluator.js CHANGED
@@ -114,9 +114,20 @@ class Evaluator {
114
114
  return result;
115
115
  }
116
116
 
117
- evalTemplateLiteral(node, env) {
118
- return node.value;
117
+ evalTemplateLiteral(node, env) {
118
+ // node.parts is an array from parser: Literal or expression nodes
119
+ let result = '';
120
+ for (const part of node.parts) {
121
+ if (part.type === 'Literal') {
122
+ result += part.value;
123
+ } else {
124
+ const val = this.evaluate(part, env);
125
+ result += val != null ? val.toString() : '';
126
+ }
119
127
  }
128
+ return result;
129
+ }
130
+
120
131
 
121
132
  evalImport(node, env) {
122
133
  const spec = node.path;
package/src/lexer.js CHANGED
@@ -25,7 +25,7 @@ class Lexer {
25
25
  skipComment() {
26
26
  if (this.currentChar === '#') {
27
27
  if (this.peek() === '*') {
28
- this.advance(); this.advance(); // skip #*
28
+ this.advance(); this.advance(); // #*
29
29
  while (this.currentChar !== null) {
30
30
  if (this.currentChar === '*' && this.peek() === '#') {
31
31
  this.advance(); this.advance();
@@ -33,7 +33,7 @@ class Lexer {
33
33
  }
34
34
  this.advance();
35
35
  }
36
- this.error("Unterminated multi-line comment (#* ... *#)");
36
+ this.error('Unterminated multi-line comment (#* ... *#)');
37
37
  } else {
38
38
  while (this.currentChar && this.currentChar !== '\n') this.advance();
39
39
  }
@@ -48,7 +48,8 @@ class Lexer {
48
48
  }
49
49
 
50
50
  if (this.currentChar === '.' && /[0-9]/.test(this.peek())) {
51
- result += '.'; this.advance();
51
+ result += '.';
52
+ this.advance();
52
53
  while (this.currentChar && /[0-9]/.test(this.currentChar)) {
53
54
  result += this.currentChar;
54
55
  this.advance();
@@ -56,11 +57,13 @@ class Lexer {
56
57
  }
57
58
 
58
59
  if (this.currentChar && /[eE]/.test(this.currentChar)) {
59
- result += this.currentChar; this.advance();
60
+ result += this.currentChar;
61
+ this.advance();
60
62
  if (this.currentChar === '+' || this.currentChar === '-') {
61
- result += this.currentChar; this.advance();
63
+ result += this.currentChar;
64
+ this.advance();
62
65
  }
63
- if (!/[0-9]/.test(this.currentChar)) this.error("Invalid exponent in number");
66
+ if (!/[0-9]/.test(this.currentChar)) this.error('Invalid exponent in number');
64
67
  while (this.currentChar && /[0-9]/.test(this.currentChar)) {
65
68
  result += this.currentChar;
66
69
  this.advance();
@@ -79,8 +82,9 @@ class Lexer {
79
82
 
80
83
  const keywords = [
81
84
  'let', 'sldeploy', 'if', 'else', 'while', 'for',
82
- 'break', 'continue', 'func', 'return', 'true', 'false', 'null',
83
- 'ask', 'define', 'import', 'from', 'as', 'undefined'
85
+ 'break', 'continue', 'func', 'return',
86
+ 'true', 'false', 'null', 'undefined',
87
+ 'ask', 'define', 'import', 'from', 'as'
84
88
  ];
85
89
 
86
90
  if (keywords.includes(result)) {
@@ -98,59 +102,58 @@ class Lexer {
98
102
  while (this.currentChar && this.currentChar !== quote) {
99
103
  if (this.currentChar === '\\') {
100
104
  this.advance();
101
- switch (this.currentChar) {
102
- case 'n': result += '\n'; break;
103
- case 't': result += '\t'; break;
104
- case '"': result += '"'; break;
105
- case "'": result += "'"; break;
106
- case '\\': result += '\\'; break;
107
- default: result += this.currentChar;
108
- }
105
+ const map = { n: '\n', t: '\t', '"': '"', "'": "'", '\\': '\\' };
106
+ result += map[this.currentChar] ?? this.currentChar;
109
107
  } else {
110
108
  result += this.currentChar;
111
109
  }
112
110
  this.advance();
113
111
  }
114
112
 
115
- if (this.currentChar !== quote) {
116
- this.error('Unterminated string literal');
117
- }
113
+ if (this.currentChar !== quote) this.error('Unterminated string literal');
118
114
 
119
115
  this.advance();
120
116
  return { type: 'STRING', value: result };
121
117
  }
122
118
 
123
- templateString() {
124
- this.advance();
125
- let result = '';
126
- while (this.currentChar && this.currentChar !== '`') {
119
+
120
+ templateString(tokens) {
121
+ this.advance(); // skip opening `
122
+
123
+ let buffer = '';
124
+
125
+ while (this.currentChar !== null) {
126
+ if (this.currentChar === '`') {
127
+ if (buffer.length > 0) {
128
+ tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
129
+ }
130
+ this.advance();
131
+ return;
132
+ }
133
+
127
134
  if (this.currentChar === '$' && this.peek() === '{') {
128
- this.advance(); this.advance(); // skip ${
129
- result += '${'; // we leave the interpolation as-is for parser
130
- continue;
135
+ if (buffer.length > 0) {
136
+ tokens.push({ type: 'TEMPLATE_STRING', value: buffer });
137
+ buffer = '';
138
+ }
139
+ this.advance(); // $
140
+ this.advance(); // {
141
+ tokens.push({ type: 'DOLLAR_LBRACE' });
142
+ return;
131
143
  }
132
144
 
133
145
  if (this.currentChar === '\\') {
134
146
  this.advance();
135
- switch (this.currentChar) {
136
- case 'n': result += '\n'; break;
137
- case 't': result += '\t'; break;
138
- case '`': result += '`'; break;
139
- case '\\': result += '\\'; break;
140
- default: result += this.currentChar;
141
- }
142
- } else {
143
- result += this.currentChar;
147
+ buffer += this.currentChar;
148
+ this.advance();
149
+ continue;
144
150
  }
145
- this.advance();
146
- }
147
151
 
148
- if (this.currentChar !== '`') {
149
- this.error('Unterminated template string');
152
+ buffer += this.currentChar;
153
+ this.advance();
150
154
  }
151
155
 
152
- this.advance();
153
- return { type: 'TEMPLATE_STRING', value: result };
156
+ this.error('Unterminated template string');
154
157
  }
155
158
 
156
159
  getTokens() {
@@ -162,7 +165,11 @@ class Lexer {
162
165
  if (/[0-9]/.test(this.currentChar)) { tokens.push(this.number()); continue; }
163
166
  if (/[A-Za-z_]/.test(this.currentChar)) { tokens.push(this.identifier()); continue; }
164
167
  if (this.currentChar === '"' || this.currentChar === "'") { tokens.push(this.string()); continue; }
165
- if (this.currentChar === '`') { tokens.push(this.templateString()); continue; }
168
+
169
+ if (this.currentChar === '`') {
170
+ this.templateString(tokens);
171
+ continue;
172
+ }
166
173
 
167
174
  const char = this.currentChar;
168
175
  const next = this.peek();
@@ -182,20 +189,30 @@ class Lexer {
182
189
  if (char === '+' && next === '+') { tokens.push({ type: 'PLUSPLUS' }); this.advance(); this.advance(); continue; }
183
190
  if (char === '-' && next === '-') { tokens.push({ type: 'MINUSMINUS' }); this.advance(); this.advance(); continue; }
184
191
 
185
- const map = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
186
- if (next === '=' && map[char]) { tokens.push({ type: map[char] }); this.advance(); this.advance(); continue; }
192
+ const compound = { '+': 'PLUSEQ', '-': 'MINUSEQ', '*': 'STAREQ', '/': 'SLASHEQ', '%': 'MODEQ' };
193
+ if (next === '=' && compound[char]) {
194
+ tokens.push({ type: compound[char] });
195
+ this.advance(); this.advance();
196
+ continue;
197
+ }
187
198
 
188
199
  const singles = {
189
200
  '+': 'PLUS', '-': 'MINUS', '*': 'STAR', '/': 'SLASH', '%': 'MOD',
190
201
  '=': 'EQUAL', '<': 'LT', '>': 'GT', '!': 'NOT',
191
- '(': 'LPAREN', ')': 'RPAREN', '{': 'LBRACE', '}': 'RBRACE',
192
- ';': 'SEMICOLON', ',': 'COMMA', '[': 'LBRACKET', ']': 'RBRACKET',
202
+ '(': 'LPAREN', ')': 'RPAREN',
203
+ '{': 'LBRACE', '}': 'RBRACE',
204
+ '[': 'LBRACKET', ']': 'RBRACKET',
205
+ ';': 'SEMICOLON', ',': 'COMMA',
193
206
  ':': 'COLON', '.': 'DOT'
194
207
  };
195
208
 
196
- if (singles[char]) { tokens.push({ type: singles[char] }); this.advance(); continue; }
209
+ if (singles[char]) {
210
+ tokens.push({ type: singles[char] });
211
+ this.advance();
212
+ continue;
213
+ }
197
214
 
198
- this.error("Unexpected character: " + char);
215
+ this.error(`Unexpected character: ${char}`);
199
216
  }
200
217
 
201
218
  tokens.push({ type: 'EOF' });
package/src/parser.js CHANGED
@@ -224,6 +224,23 @@ class Parser {
224
224
  if (this.current.type === 'SEMICOLON') this.eat('SEMICOLON');
225
225
  return { type: 'ExpressionStatement', expression: expr };
226
226
  }
227
+ parseTemplateLiteral() {
228
+ const parts = [];
229
+ while (true) {
230
+ if (this.current.type === 'TEMPLATE_STRING') {
231
+ parts.push({ type: 'Literal', value: this.current.value });
232
+ this.eat('TEMPLATE_STRING');
233
+ } else if (this.current.type === 'DOLLAR_LBRACE') {
234
+ this.eat('DOLLAR_LBRACE');
235
+ const expr = this.expression();
236
+ parts.push(expr);
237
+ this.eat('RBRACE');
238
+ } else {
239
+ break;
240
+ }
241
+ }
242
+ return { type: 'TemplateLiteral', parts };
243
+ }
227
244
 
228
245
  expression() {
229
246
  return this.assignment();
@@ -371,7 +388,11 @@ class Parser {
371
388
 
372
389
  if (t.type === 'NUMBER') { this.eat('NUMBER'); return { type: 'Literal', value: t.value }; }
373
390
  if (t.type === 'STRING') { this.eat('STRING'); return { type: 'Literal', value: t.value }; }
374
- if (t.type === 'TEMPLATE_STRING') { this.eat('TEMPLATE_STRING'); return { type: 'TemplateLiteral', value: t.value }; }
391
+ if (t.type === 'TEMPLATE_STRING') {
392
+ return this.parseTemplateLiteral();
393
+ }
394
+
395
+
375
396
  if (t.type === 'TRUE') { this.eat('TRUE'); return { type: 'Literal', value: true }; }
376
397
  if (t.type === 'FALSE') { this.eat('FALSE'); return { type: 'Literal', value: false }; }
377
398
  if (t.type === 'NULL') { this.eat('NULL'); return { type: 'Literal', value: null }; }
package/src/starlight.js CHANGED
@@ -9,7 +9,7 @@ const Lexer = require('./lexer');
9
9
  const Parser = require('./parser');
10
10
  const Evaluator = require('./evaluator');
11
11
 
12
- const VERSION = '1.0.18';
12
+ const VERSION = '1.0.20';
13
13
 
14
14
  const COLOR = {
15
15
  reset: '\x1b[0m',