@sap/cds-compiler 3.1.2 → 3.4.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.md +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +53 -0
- package/lib/checks/defaultValues.js +4 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
package/lib/compiler/checks.js
CHANGED
|
@@ -18,6 +18,8 @@ const builtins = require('../compiler/builtins');
|
|
|
18
18
|
const {
|
|
19
19
|
forEachGeneric, forEachDefinition, forEachMember,
|
|
20
20
|
} = require('../base/model');
|
|
21
|
+
const { CompilerAssertion } = require('../base/error');
|
|
22
|
+
const { pathName } = require('./utils');
|
|
21
23
|
|
|
22
24
|
function check( model ) { // = XSN
|
|
23
25
|
const {
|
|
@@ -52,7 +54,7 @@ function check( model ) { // = XSN
|
|
|
52
54
|
checkLocalizedSubElement(elem);
|
|
53
55
|
if (elem.key && elem.key.val) {
|
|
54
56
|
if (elem.virtual && elem.virtual.val)
|
|
55
|
-
error(null, [ elem.location, elem ], 'Element can\'t be virtual and key');
|
|
57
|
+
error(null, [ elem.location, elem ], {}, 'Element can\'t be virtual and key');
|
|
56
58
|
|
|
57
59
|
checkForUnmanagedAssociations( elem, elem.key );
|
|
58
60
|
}
|
|
@@ -66,26 +68,26 @@ function check( model ) { // = XSN
|
|
|
66
68
|
forEachGeneric( elem, 'elements', checkElement );
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
function checkName( construct ) {
|
|
71
|
+
function checkName( construct ) { // TODO: move to define.js
|
|
70
72
|
if (model.options.$skipNameCheck)
|
|
71
73
|
return;
|
|
72
74
|
// TODO: Move a corrected version of this check to definer (but do not rely
|
|
73
75
|
// on it!): The code below misses to consider CSN input.
|
|
74
76
|
if (construct.name.id && construct.name.id.indexOf('.') !== -1) {
|
|
75
77
|
// TODO: No, we should not forbid this
|
|
76
|
-
error(null, [ construct.name.location, construct ],
|
|
78
|
+
error(null, [ construct.name.location, construct ], {},
|
|
77
79
|
'The character \'.\' is not allowed in identifiers');
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
// TODO: move into definer.js
|
|
82
83
|
function checkLocalizedElement(elem) {
|
|
83
84
|
// if it is directly a localized element
|
|
84
85
|
if (elem.localized && elem.localized.val) {
|
|
85
86
|
const type = elem._effectiveType;
|
|
87
|
+
// See discussion issue #6520: should we allow all scalar types?
|
|
86
88
|
if (!type || !type.builtin || type.category !== 'string') {
|
|
87
|
-
warning(null, [ elem.
|
|
88
|
-
'Keyword
|
|
89
|
+
warning(null, [ elem.type?.location, elem ], { keyword: 'localized' },
|
|
90
|
+
'Keyword $(KEYWORD) should only be used in combination with string types');
|
|
89
91
|
}
|
|
90
92
|
}
|
|
91
93
|
// "key" keyword at localized element in SELECT list.
|
|
@@ -159,7 +161,7 @@ function check( model ) { // = XSN
|
|
|
159
161
|
// Special handling to print a more detailed error message.
|
|
160
162
|
// Other cases like `null` as enum value are handled in `checkEnumValueType()`
|
|
161
163
|
if (type === 'enum') {
|
|
162
|
-
warning('enum-value-ref', [ loc, enumNode ],
|
|
164
|
+
warning('enum-value-ref', [ loc, enumNode ], {},
|
|
163
165
|
'References to other values are not allowed as enum values');
|
|
164
166
|
return;
|
|
165
167
|
}
|
|
@@ -339,6 +341,7 @@ function check( model ) { // = XSN
|
|
|
339
341
|
}
|
|
340
342
|
|
|
341
343
|
// Check that min and max cardinalities of 'elem' in 'art' have legal values
|
|
344
|
+
// TODO: move to define.js or parsers
|
|
342
345
|
function checkCardinality(elem) {
|
|
343
346
|
if (!elem.cardinality)
|
|
344
347
|
return;
|
|
@@ -382,13 +385,16 @@ function check( model ) { // = XSN
|
|
|
382
385
|
|
|
383
386
|
// If provided, min cardinality must not exceed max cardinality (note that
|
|
384
387
|
// '*' is considered to be >= any number)
|
|
385
|
-
const pair = [ [ 'sourceMin', 'sourceMax', '
|
|
388
|
+
const pair = [ [ 'sourceMin', 'sourceMax', 'source' ], [ 'targetMin', 'targetMax', 'target' ] ];
|
|
386
389
|
pair.forEach((p) => {
|
|
387
390
|
if (elem.cardinality[p[0]] && elem.cardinality[p[1]] &&
|
|
388
391
|
elem.cardinality[p[1]].literal === 'number' &&
|
|
389
392
|
elem.cardinality[p[0]].val > elem.cardinality[p[1]].val) {
|
|
390
|
-
error(null, [ elem.cardinality.location, elem ],
|
|
391
|
-
|
|
393
|
+
error(null, [ elem.cardinality.location, elem ], { '#': p[2] }, {
|
|
394
|
+
std: 'Minimum cardinality must not be greater than maximum cardinality', // variant unused
|
|
395
|
+
source: 'Source minimum cardinality must not be greater than source maximum cardinality',
|
|
396
|
+
target: 'Target minimum cardinality must not be greater than target maximum cardinality',
|
|
397
|
+
});
|
|
392
398
|
}
|
|
393
399
|
});
|
|
394
400
|
}
|
|
@@ -422,7 +428,7 @@ function check( model ) { // = XSN
|
|
|
422
428
|
if (groupByEntry._artifact && groupByEntry._artifact._effectiveType &&
|
|
423
429
|
groupByEntry._artifact._effectiveType.on) {
|
|
424
430
|
// Unmanaged association - complain
|
|
425
|
-
error(null, [ groupByEntry.location, art ],
|
|
431
|
+
error(null, [ groupByEntry.location, art ], {},
|
|
426
432
|
'Unmanaged associations are not allowed in GROUP BY');
|
|
427
433
|
}
|
|
428
434
|
}
|
|
@@ -430,7 +436,7 @@ function check( model ) { // = XSN
|
|
|
430
436
|
if (orderByEntry._artifact && orderByEntry._artifact._effectiveType &&
|
|
431
437
|
orderByEntry._artifact._effectiveType.on) {
|
|
432
438
|
// Unmanaged association - complain
|
|
433
|
-
error(null, [ orderByEntry.location, art ],
|
|
439
|
+
error(null, [ orderByEntry.location, art ], {},
|
|
434
440
|
'Unmanaged associations are not allowed in ORDER BY');
|
|
435
441
|
}
|
|
436
442
|
}
|
|
@@ -459,7 +465,7 @@ function check( model ) { // = XSN
|
|
|
459
465
|
for (const k in elem.foreignKeys) {
|
|
460
466
|
const key = elem.foreignKeys[k].targetElement;
|
|
461
467
|
if (key && key._artifact && key._artifact.virtual && key._artifact.virtual.val) {
|
|
462
|
-
error(null, [ key.location, elem ],
|
|
468
|
+
error(null, [ key.location, elem ], {},
|
|
463
469
|
'Virtual elements can\'t be used as a foreign key for a managed association');
|
|
464
470
|
}
|
|
465
471
|
}
|
|
@@ -494,7 +500,7 @@ function check( model ) { // = XSN
|
|
|
494
500
|
// associations can be followed (in the ON condition)
|
|
495
501
|
//
|
|
496
502
|
// TODO: this function must be completely reworked, probably even before
|
|
497
|
-
// integration into name
|
|
503
|
+
// integration into name resolution - did the first step.
|
|
498
504
|
// It is also incomplete, as associations in structures are not checked.
|
|
499
505
|
// Additionally, `$self.assoc` references are also not found.
|
|
500
506
|
function singleCheckUnmanagedAssocCondArgumentNoFollowUnmanagedAssoc(elem, arg, op) {
|
|
@@ -517,7 +523,7 @@ function check( model ) { // = XSN
|
|
|
517
523
|
if (argTarget.on) {
|
|
518
524
|
const same = path0._artifact === elem;
|
|
519
525
|
if (!same) {
|
|
520
|
-
error(null, [ path0.location, elem ],
|
|
526
|
+
error(null, [ path0.location, elem ], {},
|
|
521
527
|
'Unmanaged association condition can\'t follow another unmanaged association');
|
|
522
528
|
}
|
|
523
529
|
}
|
|
@@ -527,7 +533,7 @@ function check( model ) { // = XSN
|
|
|
527
533
|
if (op && op.val === 'xpr') // no check for xpr
|
|
528
534
|
return;
|
|
529
535
|
if (op && op.val !== '=')
|
|
530
|
-
error(null, [ op.location, elem ], '$self comparison is only allowed with \'=\'');
|
|
536
|
+
error(null, [ op.location, elem ], {}, '$self comparison is only allowed with \'=\'');
|
|
531
537
|
}
|
|
532
538
|
|
|
533
539
|
// A function like this could be part of the compiler
|
|
@@ -601,7 +607,7 @@ function check( model ) { // = XSN
|
|
|
601
607
|
// Check for illegal argument usage within the expression
|
|
602
608
|
for (const arg of args) {
|
|
603
609
|
if (isVirtualElement(arg))
|
|
604
|
-
error(null, arg.location, 'Virtual elements can\'t be used in an expression');
|
|
610
|
+
error(null, arg.location, {}, 'Virtual elements can\'t be used in an expression');
|
|
605
611
|
|
|
606
612
|
|
|
607
613
|
// Recursively traverse the argument expression
|
|
@@ -624,20 +630,25 @@ function check( model ) { // = XSN
|
|
|
624
630
|
// Check for illegal argument usage within the expression
|
|
625
631
|
for (const arg of Array.isArray(xpr.args) && xpr.args || []) { // TODO named args?
|
|
626
632
|
if (isVirtualElement(arg))
|
|
627
|
-
error(null, arg.location, 'Virtual elements can\'t be used in an expression');
|
|
633
|
+
error(null, arg.location, {}, 'Virtual elements can\'t be used in an expression');
|
|
628
634
|
|
|
629
635
|
// Arg must not be an association and not $self
|
|
630
636
|
// Only if path is not approved exists path (that is non-query position)
|
|
631
637
|
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
|
|
632
|
-
if (arg.$expected === 'exists')
|
|
633
|
-
error(null, arg.location,
|
|
638
|
+
if (arg.$expected === 'exists') {
|
|
639
|
+
error(null, arg.location, {},
|
|
640
|
+
'An association can\'t be used as a value in an expression');
|
|
641
|
+
}
|
|
634
642
|
}
|
|
635
643
|
else if (!allowAssocTail && isAssociationOperand(arg)) {
|
|
636
|
-
error(null, arg.location,
|
|
644
|
+
error(null, arg.location, {},
|
|
645
|
+
'An association can\'t be used as a value in an expression');
|
|
637
646
|
}
|
|
638
647
|
|
|
639
|
-
if (isDollarSelfOrProjectionOperand(arg))
|
|
640
|
-
error(null, arg.location,
|
|
648
|
+
if (isDollarSelfOrProjectionOperand(arg)) {
|
|
649
|
+
error(null, arg.location, { id: arg.path[0].id },
|
|
650
|
+
'$(ID) can only be used as a value in a comparison to an association');
|
|
651
|
+
}
|
|
641
652
|
|
|
642
653
|
// Recursively traverse the argument expression
|
|
643
654
|
checkTreeLikeExpression(arg, allowAssocTail);
|
|
@@ -736,7 +747,9 @@ function check( model ) { // = XSN
|
|
|
736
747
|
|
|
737
748
|
// Element must exist in annotation
|
|
738
749
|
if (!elementDecl) {
|
|
739
|
-
warning(null, anno.location || anno.name.location,
|
|
750
|
+
warning(null, anno.location || anno.name.location,
|
|
751
|
+
{ name: pathName(anno.name.path), anno: annoDecl.name.absolute },
|
|
752
|
+
'Element $(NAME) not found for annotation $(ANNO)');
|
|
740
753
|
return;
|
|
741
754
|
}
|
|
742
755
|
|
|
@@ -747,10 +760,14 @@ function check( model ) { // = XSN
|
|
|
747
760
|
|
|
748
761
|
// Must have literal or path unless it is a boolean
|
|
749
762
|
if (!anno.literal && !anno.path && getFinalTypeNameOf(elementDecl) !== 'cds.Boolean') {
|
|
750
|
-
if (elementDecl.type && elementDecl.type._artifact.name.absolute)
|
|
751
|
-
warning(null, anno.location || anno.name.location,
|
|
752
|
-
|
|
753
|
-
|
|
763
|
+
if (elementDecl.type && elementDecl.type._artifact.name.absolute) {
|
|
764
|
+
warning(null, anno.location || anno.name.location, { type: elementDecl.type._artifact },
|
|
765
|
+
'Expecting a value of type $(TYPE) for the annotation');
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
warning(null, anno.location || anno.name.location, {},
|
|
769
|
+
'Expecting a value for the annotation');
|
|
770
|
+
}
|
|
754
771
|
|
|
755
772
|
return;
|
|
756
773
|
}
|
|
@@ -775,7 +792,7 @@ function check( model ) { // = XSN
|
|
|
775
792
|
if (elementDecl._effectiveType.items) {
|
|
776
793
|
// Make sure we have an array value
|
|
777
794
|
if (value.literal !== 'array') {
|
|
778
|
-
warning(null, loc, 'An array value is required here');
|
|
795
|
+
warning(null, loc, {}, 'An array value is required here');
|
|
779
796
|
return;
|
|
780
797
|
}
|
|
781
798
|
// Check each element
|
|
@@ -788,7 +805,7 @@ function check( model ) { // = XSN
|
|
|
788
805
|
// Struct expected (can only happen within arrays)?
|
|
789
806
|
if (elementDecl._effectiveType.elements) {
|
|
790
807
|
if (value.literal !== 'struct') {
|
|
791
|
-
warning(null, loc, 'A struct value is required here');
|
|
808
|
+
warning(null, loc, {}, 'A struct value is required here');
|
|
792
809
|
return;
|
|
793
810
|
}
|
|
794
811
|
// FIXME: Should check each element
|
|
@@ -800,31 +817,31 @@ function check( model ) { // = XSN
|
|
|
800
817
|
if (builtins.isStringTypeName(type)) {
|
|
801
818
|
if (value.literal !== 'string' && value.literal !== 'enum' &&
|
|
802
819
|
!elementDecl._effectiveType.enum)
|
|
803
|
-
warning(null, loc,
|
|
820
|
+
warning(null, loc, { type }, 'A string value is required for type $(TYPE)');
|
|
804
821
|
}
|
|
805
822
|
else if (builtins.isBinaryTypeName(type)) {
|
|
806
823
|
if (value.literal !== 'string' && value.literal !== 'x')
|
|
807
|
-
warning(null, loc,
|
|
824
|
+
warning(null, loc, { type }, 'A hexadecimal string value is required for type $(TYPE)');
|
|
808
825
|
}
|
|
809
826
|
else if (builtins.isNumericTypeName(type)) {
|
|
810
827
|
if (value.literal !== 'number' && value.literal !== 'enum' &&
|
|
811
828
|
!elementDecl._effectiveType.enum)
|
|
812
|
-
warning(null, loc,
|
|
829
|
+
warning(null, loc, { type }, 'A numerical value is required for type $(TYPE)');
|
|
813
830
|
}
|
|
814
831
|
else if (builtins.isDateOrTimeTypeName(type)) {
|
|
815
832
|
if (value.literal !== 'date' && value.literal !== 'time' &&
|
|
816
833
|
value.literal !== 'timestamp' && value.literal !== 'string')
|
|
817
|
-
warning(null, loc,
|
|
834
|
+
warning(null, loc, { type }, 'A date/time value or a string is required for type $(TYPE)');
|
|
818
835
|
}
|
|
819
836
|
else if (builtins.isBooleanTypeName(type)) {
|
|
820
837
|
if (value.literal && value.literal !== 'boolean')
|
|
821
|
-
warning(null, loc,
|
|
838
|
+
warning(null, loc, { type }, 'A boolean value is required for type $(TYPE)');
|
|
822
839
|
}
|
|
823
840
|
else if (builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
|
|
824
|
-
warning(null, loc,
|
|
841
|
+
warning(null, loc, { type }, 'Type $(TYPE) can\'t be assigned a value');
|
|
825
842
|
}
|
|
826
843
|
else {
|
|
827
|
-
throw new
|
|
844
|
+
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
|
|
828
845
|
}
|
|
829
846
|
|
|
830
847
|
// Check enums
|
|
@@ -834,19 +851,19 @@ function check( model ) { // = XSN
|
|
|
834
851
|
// Enum symbol provided and expected
|
|
835
852
|
if (!expectedEnum[value.sym.id]) {
|
|
836
853
|
// .. but no such constant
|
|
837
|
-
warning(null, loc,
|
|
854
|
+
warning(null, loc, { id: `#${ value.sym.id }` }, 'Enum symbol $(ID) not found in enum');
|
|
838
855
|
}
|
|
839
856
|
}
|
|
840
857
|
else {
|
|
841
858
|
// Enum symbol provided but not expected
|
|
842
|
-
warning(null, loc,
|
|
859
|
+
warning(null, loc, { id: `#${ value.sym.id }`, type }, 'Can\'t use enum symbol $(ID) for non-enum type $(TYPE)');
|
|
843
860
|
}
|
|
844
861
|
}
|
|
845
862
|
else if (expectedEnum) {
|
|
846
863
|
// Enum symbol not provided but expected
|
|
847
864
|
if (!Object.keys(expectedEnum).some(symbol => expectedEnum[symbol].value.val === value.val)) {
|
|
848
865
|
// ... and none of the valid enum symbols matches the value
|
|
849
|
-
warning(null, loc, 'An enum value is required here');
|
|
866
|
+
warning(null, loc, {}, 'An enum value is required here');
|
|
850
867
|
}
|
|
851
868
|
}
|
|
852
869
|
}
|