goldstein 5.1.0 → 5.3.0
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/ChangeLog +18 -0
- package/README.md +19 -9
- package/build/parser.cjs +118 -17
- package/package.json +7 -4
- package/packages/convert/index.js +1 -4
- package/packages/goldstein/index.js +2 -0
- package/packages/goldstein/parser.js +2 -0
- package/packages/keyword-fn/index.js +1 -4
- package/packages/keyword-freeze/index.js +1 -4
- package/packages/keyword-if/index.js +2 -1
- package/packages/keyword-no-async/index.js +36 -0
- package/packages/keyword-try/index.js +1 -1
- package/packages/parser/index.js +7 -7
- package/packages/printer/visitors/await-expression.js +5 -2
package/ChangeLog
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
2024.05.06, v5.3.0
|
|
2
|
+
|
|
3
|
+
feature:
|
|
4
|
+
- 98f3048 goldstein: add ability to parse no async code with await
|
|
5
|
+
- 45c7363 @putout/plugin-goldstein: eslint-plugin-putout v22.7.0
|
|
6
|
+
- 3964832 @putout/plugin-goldstein: @putout/test v9.1.0
|
|
7
|
+
- aa689c7 @putout/plugin-goldstein: eslint v9.2.0
|
|
8
|
+
- 1399854 @putout/plugin-goldstein: c8 v9.1.0
|
|
9
|
+
- a728422 goldstein: redlint v3.14.1
|
|
10
|
+
- 6f03127 goldstein: eslint v9.2.0
|
|
11
|
+
- 23c8eb3 goldstein: @putout/plugin-declare v4.0.0
|
|
12
|
+
|
|
13
|
+
2024.02.20, v5.2.0
|
|
14
|
+
|
|
15
|
+
feature:
|
|
16
|
+
- 5728421 goldstein: improve support of comments
|
|
17
|
+
- 0d997c3 goldstein: printer: visitors: await-expression: maybeVisitor
|
|
18
|
+
|
|
1
19
|
2024.02.19, v5.1.0
|
|
2
20
|
|
|
3
21
|
feature:
|
package/README.md
CHANGED
|
@@ -100,10 +100,7 @@ By default, all keywords mentioned in the next section used, but you can limit t
|
|
|
100
100
|
You can add any keywords, and even create your own:
|
|
101
101
|
|
|
102
102
|
```js
|
|
103
|
-
import {
|
|
104
|
-
compile,
|
|
105
|
-
keywords,
|
|
106
|
-
} from 'goldstein';
|
|
103
|
+
import {compile, keywords} from 'goldstein';
|
|
107
104
|
|
|
108
105
|
const source = `
|
|
109
106
|
fn hello() {
|
|
@@ -185,10 +182,7 @@ parse(`
|
|
|
185
182
|
You can make any modifications to **Goldstein AST** and then `print` back to **Goldstein**:
|
|
186
183
|
|
|
187
184
|
```js
|
|
188
|
-
import {
|
|
189
|
-
parse,
|
|
190
|
-
print,
|
|
191
|
-
} from 'goldstein';
|
|
185
|
+
import {parse, print} from 'goldstein';
|
|
192
186
|
|
|
193
187
|
const ast = parse(`const t = try f('hello')`);
|
|
194
188
|
const source = print(ast);
|
|
@@ -302,7 +296,7 @@ Is the same as:
|
|
|
302
296
|
```js
|
|
303
297
|
import tryToCatch from 'try-catch';
|
|
304
298
|
|
|
305
|
-
const [error, result] = await tryToCatch(
|
|
299
|
+
const [error, result] = await tryToCatch(hello, 'world');
|
|
306
300
|
```
|
|
307
301
|
|
|
308
302
|
### `should`
|
|
@@ -413,6 +407,22 @@ That absolutely fine, it will be converted to:
|
|
|
413
407
|
function hello() {}
|
|
414
408
|
```
|
|
415
409
|
|
|
410
|
+
### `asyn`-less `Function` with `await`
|
|
411
|
+
|
|
412
|
+
```gs
|
|
413
|
+
function hello() {
|
|
414
|
+
await world();
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
In js:
|
|
419
|
+
|
|
420
|
+
```js
|
|
421
|
+
async function hello() {
|
|
422
|
+
await world();
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
416
426
|
## How to contribute?
|
|
417
427
|
|
|
418
428
|
Clone the registry, create a new keyword with a prefix `keyword-`, then create directory `fixture` and put there two files with extensions `.js` and `.gs`. Half way done 🥳!
|
package/build/parser.cjs
CHANGED
|
@@ -1134,6 +1134,7 @@ pp$8.parseLabeledStatement = function(node, maybeName, expr, context) {
|
|
|
1134
1134
|
};
|
|
1135
1135
|
pp$8.parseExpressionStatement = function(node, expr) {
|
|
1136
1136
|
node.expression = expr;
|
|
1137
|
+
console.log(expr);
|
|
1137
1138
|
this.semicolon();
|
|
1138
1139
|
return this.finishNode(node, "ExpressionStatement");
|
|
1139
1140
|
};
|
|
@@ -5380,6 +5381,75 @@ Parser.acorn = {
|
|
|
5380
5381
|
nonASCIIwhitespace
|
|
5381
5382
|
};
|
|
5382
5383
|
|
|
5384
|
+
// node_modules/estree-util-attach-comments/lib/index.js
|
|
5385
|
+
var own = {}.hasOwnProperty;
|
|
5386
|
+
var emptyComments = [];
|
|
5387
|
+
function attachComments(tree, comments) {
|
|
5388
|
+
const list = comments ? [...comments].sort(compare) : emptyComments;
|
|
5389
|
+
if (list.length > 0)
|
|
5390
|
+
walk(tree, { comments: list, index: 0 });
|
|
5391
|
+
}
|
|
5392
|
+
function walk(node, state) {
|
|
5393
|
+
if (state.index === state.comments.length) {
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
const children = [];
|
|
5397
|
+
const comments = [];
|
|
5398
|
+
let key;
|
|
5399
|
+
for (key in node) {
|
|
5400
|
+
if (own.call(node, key)) {
|
|
5401
|
+
const value = node[key];
|
|
5402
|
+
if (value && typeof value === "object" && key !== "comments") {
|
|
5403
|
+
if (Array.isArray(value)) {
|
|
5404
|
+
let index2 = -1;
|
|
5405
|
+
while (++index2 < value.length) {
|
|
5406
|
+
if (value[index2] && typeof value[index2].type === "string") {
|
|
5407
|
+
children.push(value[index2]);
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5410
|
+
} else if (typeof value.type === "string") {
|
|
5411
|
+
children.push(value);
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
children.sort(compare);
|
|
5417
|
+
comments.push(...slice(state, node, false, { leading: true, trailing: false }));
|
|
5418
|
+
let index = -1;
|
|
5419
|
+
while (++index < children.length) {
|
|
5420
|
+
walk(children[index], state);
|
|
5421
|
+
}
|
|
5422
|
+
comments.push(
|
|
5423
|
+
...slice(state, node, true, {
|
|
5424
|
+
leading: false,
|
|
5425
|
+
trailing: children.length > 0
|
|
5426
|
+
})
|
|
5427
|
+
);
|
|
5428
|
+
if (comments.length > 0) {
|
|
5429
|
+
node.comments = comments;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
function slice(state, node, compareEnd, fields) {
|
|
5433
|
+
const result = [];
|
|
5434
|
+
while (state.comments[state.index] && compare(state.comments[state.index], node, compareEnd) < 1) {
|
|
5435
|
+
result.push(Object.assign({}, state.comments[state.index++], fields));
|
|
5436
|
+
}
|
|
5437
|
+
return result;
|
|
5438
|
+
}
|
|
5439
|
+
function compare(left, right, compareEnd) {
|
|
5440
|
+
const field = compareEnd ? "end" : "start";
|
|
5441
|
+
if (left.range && right.range) {
|
|
5442
|
+
return left.range[0] - right.range[compareEnd ? 1 : 0];
|
|
5443
|
+
}
|
|
5444
|
+
if (left.loc && left.loc.start && right.loc && right.loc[field]) {
|
|
5445
|
+
return left.loc.start.line - right.loc[field].line || left.loc.start.column - right.loc[field].column;
|
|
5446
|
+
}
|
|
5447
|
+
if ("start" in left && field in right) {
|
|
5448
|
+
return left.start - right[field];
|
|
5449
|
+
}
|
|
5450
|
+
return Number.NaN;
|
|
5451
|
+
}
|
|
5452
|
+
|
|
5383
5453
|
// packages/parser/index.js
|
|
5384
5454
|
var extendParser = (keywords3) => {
|
|
5385
5455
|
const parser = Parser.extend(...keywords3);
|
|
@@ -5396,15 +5466,13 @@ var createParse = (parser) => (source) => {
|
|
|
5396
5466
|
locations: true,
|
|
5397
5467
|
comment: true,
|
|
5398
5468
|
ranges: true,
|
|
5399
|
-
onComment:
|
|
5400
|
-
comments.push(a);
|
|
5401
|
-
}
|
|
5469
|
+
onComment: comments
|
|
5402
5470
|
};
|
|
5403
|
-
const
|
|
5404
|
-
|
|
5471
|
+
const ast = parser.parse(source, options);
|
|
5472
|
+
attachComments(ast, comments);
|
|
5405
5473
|
return {
|
|
5406
|
-
...
|
|
5407
|
-
tokens,
|
|
5474
|
+
...ast,
|
|
5475
|
+
tokens: Array.from(parser.tokenizer(source, options)),
|
|
5408
5476
|
comments
|
|
5409
5477
|
};
|
|
5410
5478
|
};
|
|
@@ -5881,15 +5949,18 @@ function createIfLet({ isParenL }) {
|
|
|
5881
5949
|
const node = {
|
|
5882
5950
|
loc: {},
|
|
5883
5951
|
range: [],
|
|
5884
|
-
body: [
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5952
|
+
body: [
|
|
5953
|
+
{
|
|
5954
|
+
type: "VariableDeclaration",
|
|
5955
|
+
kind: "let",
|
|
5956
|
+
declarations: [{
|
|
5957
|
+
type: "VariableDeclarator",
|
|
5958
|
+
id: assignmentExpression.left,
|
|
5959
|
+
init: assignmentExpression.right
|
|
5960
|
+
}]
|
|
5961
|
+
},
|
|
5962
|
+
ifNode
|
|
5963
|
+
]
|
|
5893
5964
|
};
|
|
5894
5965
|
return this.finishNode(node, "BlockStatement");
|
|
5895
5966
|
}
|
|
@@ -6042,6 +6113,35 @@ function createAppendNode(context, node) {
|
|
|
6042
6113
|
return context.finishNode(node, "CallExpression");
|
|
6043
6114
|
}
|
|
6044
6115
|
|
|
6116
|
+
// packages/keyword-no-async/index.js
|
|
6117
|
+
var { defineProperty } = Object;
|
|
6118
|
+
function fn4(Parser3) {
|
|
6119
|
+
return class extends Parser3 {
|
|
6120
|
+
parseFunction(node, statement, allowExpressionBody, isAsync, forInit) {
|
|
6121
|
+
return super.parseFunction(node, statement, allowExpressionBody, true, forInit);
|
|
6122
|
+
}
|
|
6123
|
+
checkUnreserved({ start, end, name }) {
|
|
6124
|
+
if (this.inGenerator && name === "yield")
|
|
6125
|
+
this.raiseRecoverable(start, `Cannot use 'yield' as identifier inside a generator`);
|
|
6126
|
+
if (this.inAsync && name === "await")
|
|
6127
|
+
this.raiseRecoverable(start, `Cannot use 'await' as identifier inside an async function`);
|
|
6128
|
+
if (this.currentThisScope().inClassFieldInit && name === "arguments")
|
|
6129
|
+
this.raiseRecoverable(start, `Cannot use 'arguments' in class field initializer`);
|
|
6130
|
+
if (this.inClassStaticBlock && (name === "arguments" || name === "await"))
|
|
6131
|
+
this.raise(start, `Cannot use ${name} in class static initialization block`);
|
|
6132
|
+
if (this.keywords.test(name))
|
|
6133
|
+
this.raise(start, `Unexpected keyword '` + name + `'`);
|
|
6134
|
+
if (this.options.ecmaVersion < 6 && this.input.slice(start, end).includes("\\"))
|
|
6135
|
+
return;
|
|
6136
|
+
const re = this.strict ? this.reservedWordsStrict : this.reservedWords;
|
|
6137
|
+
if (re.test(name) && !this.inAsync && name === "await")
|
|
6138
|
+
defineProperty(this, "inAsync", {
|
|
6139
|
+
value: true
|
|
6140
|
+
});
|
|
6141
|
+
}
|
|
6142
|
+
};
|
|
6143
|
+
}
|
|
6144
|
+
|
|
6045
6145
|
// packages/goldstein/parser.js
|
|
6046
6146
|
var defaultKeywords = {
|
|
6047
6147
|
keywordFn: fn,
|
|
@@ -6055,7 +6155,8 @@ var defaultKeywords = {
|
|
|
6055
6155
|
keywordImport,
|
|
6056
6156
|
keywordArrow: fn3,
|
|
6057
6157
|
keywordAddArray,
|
|
6058
|
-
stringInterpolation
|
|
6158
|
+
stringInterpolation,
|
|
6159
|
+
keywordNoAsync: fn4
|
|
6059
6160
|
};
|
|
6060
6161
|
var keywords2 = defaultKeywords;
|
|
6061
6162
|
var parse3 = (source, options = {}, keywords3 = defaultKeywords) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goldstein",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
|
|
6
6
|
"description": "JavaScript with no limits",
|
|
@@ -29,14 +29,16 @@
|
|
|
29
29
|
"wisdom": "madrun wisdom"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@putout/plugin-declare": "^
|
|
32
|
+
"@putout/plugin-declare": "^4.0.0",
|
|
33
33
|
"@putout/plugin-logical-expressions": "^5.0.0",
|
|
34
34
|
"@putout/plugin-try-catch": "^3.0.0",
|
|
35
|
+
"@putout/plugin-promises": "^14.1.0",
|
|
35
36
|
"@putout/printer": "^8.0.1",
|
|
36
37
|
"acorn": "^8.7.1",
|
|
37
38
|
"esbuild": "^0.20.1",
|
|
38
39
|
"esbuild-node-builtins": "^0.1.0",
|
|
39
40
|
"estree-to-babel": "^9.0.0",
|
|
41
|
+
"estree-util-attach-comments": "^3.0.0",
|
|
40
42
|
"putout": "^35.0.0",
|
|
41
43
|
"try-catch": "^3.0.1"
|
|
42
44
|
},
|
|
@@ -44,17 +46,18 @@
|
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@babel/core": "^8.0.0-alpha.5",
|
|
46
48
|
"@cloudcmd/stub": "^4.0.1",
|
|
49
|
+
"@putout/plugin-goldstein": "./rules/goldstein",
|
|
47
50
|
"@putout/test": "^9.0.1",
|
|
48
51
|
"c8": "^9.1.0",
|
|
49
52
|
"check-dts": "^0.7.1",
|
|
50
53
|
"escover": "^4.0.1",
|
|
51
|
-
"eslint": "^
|
|
52
|
-
"eslint-plugin-n": "^16.0.1",
|
|
54
|
+
"eslint": "^9.2.0",
|
|
53
55
|
"eslint-plugin-putout": "^22.0.0",
|
|
54
56
|
"madrun": "^10.0.0",
|
|
55
57
|
"mock-require": "^3.0.3",
|
|
56
58
|
"montag": "^1.2.1",
|
|
57
59
|
"nodemon": "^3.0.1",
|
|
60
|
+
"redlint": "^3.14.1",
|
|
58
61
|
"runsome": "^1.0.0",
|
|
59
62
|
"supertape": "^10.1.0",
|
|
60
63
|
"typescript": "^5.0.3"
|
|
@@ -5,10 +5,7 @@ import * as removeImportTry from './remove-import-try/index.js';
|
|
|
5
5
|
import * as applyTry from './apply-try/index.js';
|
|
6
6
|
import * as addArray from './add-array/index.js';
|
|
7
7
|
import * as applyIfLet from './apply-if-let/index.js';
|
|
8
|
-
import {
|
|
9
|
-
fixEmpty,
|
|
10
|
-
parse,
|
|
11
|
-
} from '../goldstein/index.js';
|
|
8
|
+
import {fixEmpty, parse} from '../goldstein/index.js';
|
|
12
9
|
|
|
13
10
|
export const convert = (source) => {
|
|
14
11
|
const ast = estreeToBabel(parse(source));
|
|
@@ -3,6 +3,7 @@ import {print} from '@putout/printer';
|
|
|
3
3
|
import tryCatchPlugin from '@putout/plugin-try-catch';
|
|
4
4
|
import declarePlugin from '@putout/plugin-declare';
|
|
5
5
|
import logicalExpressionsPlugin from '@putout/plugin-logical-expressions';
|
|
6
|
+
import promisesPlugin from '@putout/plugin-promises';
|
|
6
7
|
import {parse} from './parser.js';
|
|
7
8
|
|
|
8
9
|
export * from './parser.js';
|
|
@@ -19,6 +20,7 @@ export const compile = (source, options = {}) => {
|
|
|
19
20
|
['try-catch', tryCatchPlugin],
|
|
20
21
|
['declare', declarePlugin],
|
|
21
22
|
['logical-expressions', logicalExpressionsPlugin],
|
|
23
|
+
['promises', promisesPlugin],
|
|
22
24
|
],
|
|
23
25
|
});
|
|
24
26
|
|
|
@@ -12,6 +12,7 @@ import keywordIf from '../keyword-if/index.js';
|
|
|
12
12
|
import keywordImport from '../keyword-import/index.js';
|
|
13
13
|
import keywordArrow from '../keyword-arrow/index.js';
|
|
14
14
|
import keywordAddArray from '../keyword-add-array/index.js';
|
|
15
|
+
import keywordNoAsync from '../keyword-no-async/index.js';
|
|
15
16
|
|
|
16
17
|
const defaultKeywords = {
|
|
17
18
|
keywordFn,
|
|
@@ -26,6 +27,7 @@ const defaultKeywords = {
|
|
|
26
27
|
keywordArrow,
|
|
27
28
|
keywordAddArray,
|
|
28
29
|
stringInterpolation,
|
|
30
|
+
keywordNoAsync,
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
export const keywords = defaultKeywords;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const {defineProperty} = Object;
|
|
2
|
+
|
|
3
|
+
export default function fn(Parser) {
|
|
4
|
+
return class extends Parser {
|
|
5
|
+
parseFunction(node, statement, allowExpressionBody, isAsync, forInit) {
|
|
6
|
+
return super.parseFunction(node, statement, allowExpressionBody, true, forInit);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
checkUnreserved({start, end, name}) {
|
|
10
|
+
if (this.inGenerator && name === 'yield')
|
|
11
|
+
this.raiseRecoverable(start, `Cannot use 'yield' as identifier inside a generator`);
|
|
12
|
+
|
|
13
|
+
if (this.inAsync && name === 'await')
|
|
14
|
+
this.raiseRecoverable(start, `Cannot use 'await' as identifier inside an async function`);
|
|
15
|
+
|
|
16
|
+
if (this.currentThisScope().inClassFieldInit && name === 'arguments')
|
|
17
|
+
this.raiseRecoverable(start, `Cannot use 'arguments' in class field initializer`);
|
|
18
|
+
|
|
19
|
+
if (this.inClassStaticBlock && (name === 'arguments' || name === 'await'))
|
|
20
|
+
this.raise(start, `Cannot use ${name} in class static initialization block`);
|
|
21
|
+
|
|
22
|
+
if (this.keywords.test(name))
|
|
23
|
+
this.raise(start, `Unexpected keyword '` + name + `'`);
|
|
24
|
+
|
|
25
|
+
if (this.options.ecmaVersion < 6 && this.input.slice(start, end).includes('\\'))
|
|
26
|
+
return;
|
|
27
|
+
|
|
28
|
+
const re = this.strict ? this.reservedWordsStrict : this.reservedWords;
|
|
29
|
+
|
|
30
|
+
if (re.test(name) && !this.inAsync && name === 'await')
|
|
31
|
+
defineProperty(this, 'inAsync', {
|
|
32
|
+
value: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {types} from 'putout';
|
|
2
|
+
import {setGoldsteinTry} from '../types/try.js';
|
|
2
3
|
import {
|
|
3
4
|
BIND_LEXICAL,
|
|
4
5
|
BIND_SIMPLE_CATCH,
|
|
5
6
|
SCOPE_SIMPLE_CATCH,
|
|
6
7
|
tokTypes as tt,
|
|
7
8
|
} from '../operator/index.js';
|
|
8
|
-
import {setGoldsteinTry} from '../types/try.js';
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
isCallExpression,
|
package/packages/parser/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {Parser} from 'acorn';
|
|
2
|
+
import {attachComments} from 'estree-util-attach-comments';
|
|
2
3
|
|
|
3
4
|
export const extendParser = (keywords) => {
|
|
4
5
|
const parser = Parser.extend(...keywords);
|
|
@@ -17,17 +18,16 @@ const createParse = (parser) => (source) => {
|
|
|
17
18
|
locations: true,
|
|
18
19
|
comment: true,
|
|
19
20
|
ranges: true,
|
|
20
|
-
onComment:
|
|
21
|
-
comments.push(a);
|
|
22
|
-
},
|
|
21
|
+
onComment: comments,
|
|
23
22
|
};
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
24
|
+
const ast = parser.parse(source, options);
|
|
25
|
+
|
|
26
|
+
attachComments(ast, comments);
|
|
27
27
|
|
|
28
28
|
return {
|
|
29
|
-
...
|
|
30
|
-
tokens,
|
|
29
|
+
...ast,
|
|
30
|
+
tokens: Array.from(parser.tokenizer(source, options)),
|
|
31
31
|
comments,
|
|
32
32
|
};
|
|
33
33
|
};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
maybeVisitor,
|
|
3
|
+
visitors as v,
|
|
4
|
+
} from '@putout/printer';
|
|
2
5
|
|
|
3
6
|
export const AwaitExpression = (path, printer, semantics) => {
|
|
4
7
|
const {print} = printer;
|
|
5
8
|
|
|
6
9
|
if (!path.node.goldstein)
|
|
7
|
-
return v.AwaitExpression
|
|
10
|
+
return maybeVisitor(v.AwaitExpression, path, printer, semantics);
|
|
8
11
|
|
|
9
12
|
print('__goldstein');
|
|
10
13
|
};
|