@sap/cds-compiler 3.5.4 → 3.6.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 +56 -2
- package/bin/cdsc.js +14 -6
- package/doc/CHANGELOG_ARCHIVE.md +10 -10
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +32 -55
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +104 -32
- package/lib/base/messages.js +277 -212
- package/lib/base/optionProcessorHelper.js +9 -2
- package/lib/base/shuffle.js +50 -0
- package/lib/checks/actionsFunctions.js +37 -20
- package/lib/checks/foreignKeys.js +13 -6
- package/lib/checks/nonexpandableStructured.js +1 -2
- package/lib/checks/onConditions.js +21 -19
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -0
- package/lib/checks/types.js +16 -22
- package/lib/compiler/assert-consistency.js +31 -28
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +72 -63
- package/lib/compiler/define.js +396 -314
- package/lib/compiler/extend.js +55 -49
- package/lib/compiler/index.js +5 -0
- package/lib/compiler/populate.js +28 -11
- package/lib/compiler/propagator.js +2 -1
- package/lib/compiler/resolve.js +28 -13
- package/lib/compiler/shared.js +15 -10
- package/lib/compiler/utils.js +7 -7
- package/lib/edm/annotations/genericTranslation.js +51 -46
- package/lib/edm/annotations/preprocessAnnotations.js +37 -40
- package/lib/edm/csn2edm.js +69 -21
- package/lib/edm/edm.js +2 -2
- package/lib/edm/edmInboundChecks.js +6 -8
- package/lib/edm/edmPreprocessor.js +88 -80
- package/lib/edm/edmUtils.js +6 -15
- package/lib/gen/Dictionary.json +81 -13
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4680 -4484
- package/lib/inspect/inspectModelStatistics.js +2 -1
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +131 -78
- package/lib/json/to-csn.js +39 -23
- package/lib/language/antlrParser.js +0 -3
- package/lib/language/docCommentParser.js +7 -3
- package/lib/language/errorStrategy.js +3 -2
- package/lib/language/genericAntlrParser.js +96 -41
- package/lib/language/language.g4 +112 -128
- package/lib/language/multiLineStringParser.js +2 -1
- package/lib/main.d.ts +115 -2
- package/lib/main.js +16 -3
- package/lib/model/csnRefs.js +3 -3
- package/lib/model/csnUtils.js +109 -179
- package/lib/model/enrichCsn.js +13 -8
- package/lib/model/revealInternalProperties.js +4 -3
- package/lib/optionProcessor.js +19 -3
- package/lib/render/manageConstraints.js +11 -15
- package/lib/render/toCdl.js +144 -47
- package/lib/render/toHdbcds.js +22 -22
- package/lib/render/toRename.js +3 -4
- package/lib/render/toSql.js +29 -20
- package/lib/render/utils/delta.js +3 -1
- package/lib/render/utils/sql.js +2 -14
- package/lib/transform/db/associations.js +6 -6
- package/lib/transform/db/cdsPersistence.js +3 -3
- package/lib/transform/db/constraints.js +4 -6
- package/lib/transform/db/expansion.js +4 -4
- package/lib/transform/db/flattening.js +12 -15
- package/lib/transform/db/temporal.js +4 -3
- package/lib/transform/db/transformExists.js +2 -1
- package/lib/transform/draft/db.js +7 -7
- package/lib/transform/forOdataNew.js +15 -4
- package/lib/transform/forRelationalDB.js +53 -39
- package/lib/transform/odata/toFinalBaseType.js +106 -82
- package/lib/transform/odata/typesExposure.js +26 -17
- package/lib/transform/odata/utils.js +1 -1
- package/lib/transform/parseExpr.js +1 -1
- package/lib/transform/transformUtilsNew.js +33 -10
- package/lib/transform/translateAssocsToJoins.js +8 -7
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
- package/lib/utils/timetrace.js +2 -2
- package/package.json +1 -2
package/lib/json/to-csn.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
const { locationString } = require('../base/messages');
|
|
15
15
|
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
16
16
|
const { pathName } = require('../compiler/utils');
|
|
17
|
+
const { CompilerAssertion } = require('../base/error');
|
|
17
18
|
|
|
18
19
|
const compilerVersion = require('../../package.json').version;
|
|
19
20
|
const creator = `CDS Compiler v${ compilerVersion }`;
|
|
@@ -106,7 +107,7 @@ const transformers = {
|
|
|
106
107
|
notNull: value,
|
|
107
108
|
default: expression,
|
|
108
109
|
// targetElement: ignore, // special display of foreign key, renameTo: select
|
|
109
|
-
value:
|
|
110
|
+
value: enumValueOrCalc, // do not list for select items as elements
|
|
110
111
|
query,
|
|
111
112
|
elements,
|
|
112
113
|
actions, // TODO: just normal dictionary
|
|
@@ -570,7 +571,7 @@ function standard( node ) {
|
|
|
570
571
|
function unexpected( val, csn, node, prop ) {
|
|
571
572
|
if (strictMode) {
|
|
572
573
|
const loc = val && val.location || node.location;
|
|
573
|
-
throw new
|
|
574
|
+
throw new CompilerAssertion( `Unexpected property ${ prop } in ${ locationString(loc) }` );
|
|
574
575
|
}
|
|
575
576
|
// otherwise, just ignore the unexpected property
|
|
576
577
|
}
|
|
@@ -820,7 +821,7 @@ function returns( art, csn, _node, prop ) {
|
|
|
820
821
|
}
|
|
821
822
|
|
|
822
823
|
function definition( art, _csn, _node, prop ) {
|
|
823
|
-
if (!art || typeof art !== 'object')
|
|
824
|
+
if (!art || typeof art !== 'object' || art.builtin)
|
|
824
825
|
return undefined; // TODO: complain with strict
|
|
825
826
|
// Do not include namespace definitions or inferred construct (in gensrc):
|
|
826
827
|
if (art.kind === 'namespace' || art.$inferred && gensrcFlavor)
|
|
@@ -1049,6 +1050,11 @@ function hasExplicitProp( ref, alsoLikeExplicit ) {
|
|
|
1049
1050
|
return ref && (!ref.$inferred || ref.$inferred === alsoLikeExplicit );
|
|
1050
1051
|
}
|
|
1051
1052
|
|
|
1053
|
+
/**
|
|
1054
|
+
* @param art
|
|
1055
|
+
* @param user
|
|
1056
|
+
* @return {boolean|string[]}
|
|
1057
|
+
*/
|
|
1052
1058
|
function originRef( art, user ) {
|
|
1053
1059
|
const r = [];
|
|
1054
1060
|
// do not use name.element, as we might allow `.`s in name
|
|
@@ -1143,7 +1149,7 @@ function artifactRef( node, terse ) {
|
|
|
1143
1149
|
const root = Array.isArray(link) ? link[0] : link;
|
|
1144
1150
|
if (!root) { // XSN directly coming from the parser
|
|
1145
1151
|
if (strictMode && node.scope === 'typeOf')
|
|
1146
|
-
throw new
|
|
1152
|
+
throw new CompilerAssertion( `Unexpected TYPE OF in ${ locationString(node.location) }`);
|
|
1147
1153
|
return renderArtifactPath( node, path, terse, node.scope );
|
|
1148
1154
|
}
|
|
1149
1155
|
const { absolute } = root.name;
|
|
@@ -1256,17 +1262,17 @@ function value( node ) {
|
|
|
1256
1262
|
return r;
|
|
1257
1263
|
}
|
|
1258
1264
|
|
|
1259
|
-
function
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
if
|
|
1263
|
-
|
|
1264
|
-
// (with gensrc, the symbol itself would not make it into the CSN)
|
|
1265
|
-
if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')
|
|
1265
|
+
function enumValueOrCalc( v, csn, node ) {
|
|
1266
|
+
if (v.$inferred && (universalCsn || gensrcFlavor))
|
|
1267
|
+
return undefined;
|
|
1268
|
+
// Enums can have values but if enums are extended, their kind is 'element'
|
|
1269
|
+
if (node.kind === 'enum' || node.$syntax === 'enum')
|
|
1266
1270
|
Object.assign( csn, expression( v ) );
|
|
1271
|
+
else if (node.$syntax === 'calc') // TODO: || node._parent?.kind === 'extend'
|
|
1272
|
+
return expression( v );
|
|
1273
|
+
return undefined;
|
|
1267
1274
|
}
|
|
1268
1275
|
|
|
1269
|
-
|
|
1270
1276
|
function onCondition( cond, csn, node ) {
|
|
1271
1277
|
if (gensrcFlavor) {
|
|
1272
1278
|
if (node._origin && node._origin.$inferred === 'REDIRECTED')
|
|
@@ -1280,14 +1286,14 @@ function onCondition( cond, csn, node ) {
|
|
|
1280
1286
|
function condition( node ) {
|
|
1281
1287
|
const expr = exprInternal( node, 'no' );
|
|
1282
1288
|
return (Array.isArray( expr ))
|
|
1283
|
-
?
|
|
1289
|
+
? flattenInternalXpr( expr, node.op?.val )
|
|
1284
1290
|
: !expr.cast && !expr.func && expr.xpr || [ expr ];
|
|
1285
1291
|
}
|
|
1286
1292
|
|
|
1287
1293
|
function expression( node ) {
|
|
1288
1294
|
const expr = exprInternal( node, 'no' );
|
|
1289
1295
|
return (Array.isArray( expr ))
|
|
1290
|
-
? { xpr:
|
|
1296
|
+
? { xpr: flattenInternalXpr( expr, node.op?.val ) }
|
|
1291
1297
|
: expr;
|
|
1292
1298
|
}
|
|
1293
1299
|
|
|
@@ -1331,8 +1337,9 @@ function exprInternal( node, xprParens ) {
|
|
|
1331
1337
|
if (!node.op) // parse error
|
|
1332
1338
|
return { xpr: [] };
|
|
1333
1339
|
|
|
1334
|
-
|
|
1340
|
+
let { val } = node.op;
|
|
1335
1341
|
switch (val) {
|
|
1342
|
+
case 'nary':
|
|
1336
1343
|
case 'ixpr':
|
|
1337
1344
|
case 'xpr':
|
|
1338
1345
|
break;
|
|
@@ -1346,21 +1353,30 @@ function exprInternal( node, xprParens ) {
|
|
|
1346
1353
|
const nary = [];
|
|
1347
1354
|
for (const item of node.args)
|
|
1348
1355
|
nary.push( { val, literal: 'token' }, item );
|
|
1356
|
+
val = 'nary';
|
|
1349
1357
|
node = {
|
|
1350
|
-
op: { val
|
|
1351
|
-
args: (nary.length > 2 ? nary.slice(1) : nary),
|
|
1358
|
+
op: { val },
|
|
1359
|
+
args: (nary.length > 2 ? nary.slice(1) : nary), // length 1,2 only with CSN v0
|
|
1352
1360
|
$parens: node.$parens,
|
|
1353
1361
|
};
|
|
1354
1362
|
}
|
|
1355
1363
|
}
|
|
1356
1364
|
const rargs = node.args.map( exprInternal );
|
|
1357
1365
|
if (val === 'xpr' || node.$parens)
|
|
1358
|
-
return extra( { xpr:
|
|
1359
|
-
return rargs.length === 1 ? rargs[0] : rargs;
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
function
|
|
1363
|
-
|
|
1366
|
+
return extra( { xpr: flattenInternalXpr( rargs, val ) }, node, (xprParens === 'no' ? 0 : 1) );
|
|
1367
|
+
return rargs.length === 1 ? rargs[0] : flattenInternalXpr( rargs, val );
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function flattenInternalXpr( array, op ) {
|
|
1371
|
+
if (!structXpr)
|
|
1372
|
+
return array.flat( Infinity );
|
|
1373
|
+
if (array.length < 5 || op !== 'nary')
|
|
1374
|
+
return array;
|
|
1375
|
+
let left = array.slice( 0, 3 );
|
|
1376
|
+
let index = 3;
|
|
1377
|
+
while (index < array.length)
|
|
1378
|
+
left = [ left, array[index++], array[index++] ];
|
|
1379
|
+
return left;
|
|
1364
1380
|
}
|
|
1365
1381
|
|
|
1366
1382
|
function query( node, csn, xsn, _prop, expectedParens = 0 ) {
|
|
@@ -164,11 +164,8 @@ function parse( source, filename = '<undefined>.cds',
|
|
|
164
164
|
if (options.docComment !== false) {
|
|
165
165
|
for (const token of tokenStream.tokens) {
|
|
166
166
|
if (token.type === parser.constructor.DocComment && !token.isUsed) {
|
|
167
|
-
// TODO: think of 'syntax-unexpected-doc-comment'
|
|
168
167
|
messageFunctions.info( 'syntax-ignoring-doc-comment', parser.tokenLocation(token), {},
|
|
169
168
|
'Ignoring doc comment as it is not written at a defined position' );
|
|
170
|
-
// this is also for position inside some artifact definition, i.e. previous text
|
|
171
|
-
// "does not belong to any artifact" might be confusing
|
|
172
169
|
}
|
|
173
170
|
}
|
|
174
171
|
}
|
|
@@ -6,6 +6,10 @@ const {
|
|
|
6
6
|
cdlNewLineRegEx,
|
|
7
7
|
} = require('./textUtils');
|
|
8
8
|
|
|
9
|
+
const fencedCommentRegEx = /^\s*[*]/;
|
|
10
|
+
const footerFenceRegEx = /\s*[*]+\/$/;
|
|
11
|
+
const hasContentOnFirstLineRegEx = /\/\*+\s*\S/;
|
|
12
|
+
|
|
9
13
|
/**
|
|
10
14
|
* Get the content of a JSDoc-like comment and remove all surrounding asterisks, etc.
|
|
11
15
|
* If the comment only contains whitespace it is seen as empty and `null` is returned
|
|
@@ -36,7 +40,7 @@ function parseDocComment( comment ) {
|
|
|
36
40
|
|
|
37
41
|
// If the comment already has content on the first line, i.e. after `/**`,
|
|
38
42
|
// its leading whitespace is ignored for whitespace trimming.
|
|
39
|
-
const hasContentOnFirstLine =
|
|
43
|
+
const hasContentOnFirstLine = hasContentOnFirstLineRegEx.test(lines[0]);
|
|
40
44
|
|
|
41
45
|
// First line, i.e. header, is always trimmed from left.
|
|
42
46
|
lines[0] = removeHeaderFence(lines[0]).trimStart();
|
|
@@ -139,7 +143,7 @@ function removeHeaderFence( line ) {
|
|
|
139
143
|
* @returns {string} header without fence
|
|
140
144
|
*/
|
|
141
145
|
function removeFooterFence( line ) {
|
|
142
|
-
return line.replace(
|
|
146
|
+
return line.replace(footerFenceRegEx, '');
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
@@ -151,7 +155,7 @@ function removeFooterFence( line ) {
|
|
|
151
155
|
function isFencedComment( lines ) {
|
|
152
156
|
const index = lines.findIndex((line, i) => {
|
|
153
157
|
const exclude = (i === 0 || i === lines.length - 1);
|
|
154
|
-
return !exclude && !(
|
|
158
|
+
return !exclude && !(fencedCommentRegEx.test(line));
|
|
155
159
|
});
|
|
156
160
|
return index === -1 && lines.length > 2;
|
|
157
161
|
}
|
|
@@ -38,6 +38,7 @@ const {
|
|
|
38
38
|
} = require('antlr4/src/antlr4/PredictionContext');
|
|
39
39
|
const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
|
|
40
40
|
const { IntervalSet, Interval } = require('antlr4/src/antlr4/IntervalSet');
|
|
41
|
+
const { CompilerAssertion } = require('../base/error');
|
|
41
42
|
|
|
42
43
|
const keywordRegexp = /^[a-zA-Z]+$/; // we don't have keywords with underscore
|
|
43
44
|
|
|
@@ -317,7 +318,7 @@ function consumeUntil( recognizer, set ) {
|
|
|
317
318
|
}
|
|
318
319
|
// console.log('CONSUMED:',s,this.getTokenDisplay( recognizer.getCurrentToken(),
|
|
319
320
|
// recognizer ),recognizer.getCurrentToken().line);
|
|
320
|
-
// throw new
|
|
321
|
+
// throw new CompilerAssertion('Sync')
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
324
|
|
|
@@ -471,7 +472,7 @@ function getExpectedTokensForMessage( recognizer, offendingToken, deadEnds ) {
|
|
|
471
472
|
if (recognizer.state < 0)
|
|
472
473
|
return [];
|
|
473
474
|
if (recognizer.state >= atn.states.length) {
|
|
474
|
-
throw new
|
|
475
|
+
throw new CompilerAssertion( `Invalid state number ${ recognizer.state } for ${
|
|
475
476
|
this.getTokenErrorDisplay( offendingToken ) }`);
|
|
476
477
|
}
|
|
477
478
|
|
|
@@ -111,6 +111,10 @@ Object.assign(GenericAntlrParser.prototype, {
|
|
|
111
111
|
finalizeDictOrArray,
|
|
112
112
|
createPrefixOp,
|
|
113
113
|
setMaxCardinality,
|
|
114
|
+
setNullability,
|
|
115
|
+
reportDuplicateClause,
|
|
116
|
+
reportUnexpectedExtension,
|
|
117
|
+
reportUnexpectedSpace,
|
|
114
118
|
pushIdent,
|
|
115
119
|
handleComposition,
|
|
116
120
|
associationInSelectItem,
|
|
@@ -165,6 +169,7 @@ function noSemicolonHere() {
|
|
|
165
169
|
// Using this function "during ATN decision making" has no effect
|
|
166
170
|
// In front of an ATN decision, you might specify dedicated excludes
|
|
167
171
|
// for non-LA1 tokens via a sub-array in excludes[0].
|
|
172
|
+
// TODO: consider $nextTokens…, see commented use in rule `elementProperties`
|
|
168
173
|
function excludeExpected( excludes ) {
|
|
169
174
|
if (excludes) {
|
|
170
175
|
// @ts-ignore
|
|
@@ -192,8 +197,8 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
|
|
|
192
197
|
ll1.type = this.constructor[tokenName];
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
function setLocalTokenForId( tokenNameMap ) {
|
|
196
|
-
const tokenName = tokenNameMap[this._input.LT(
|
|
200
|
+
function setLocalTokenForId( offset, tokenNameMap ) {
|
|
201
|
+
const tokenName = tokenNameMap[this._input.LT( offset ).text.toUpperCase() || ''];
|
|
197
202
|
const ll1 = this.getCurrentToken();
|
|
198
203
|
if (tokenName &&
|
|
199
204
|
(ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )))
|
|
@@ -219,9 +224,9 @@ function setLocalTokenForId( tokenNameMap ) {
|
|
|
219
224
|
function noAssignmentInSameLine() {
|
|
220
225
|
const t = this.getCurrentToken();
|
|
221
226
|
if (t.text === '@' && t.line <= this._input.LT(-1).line) {
|
|
222
|
-
this.warning( 'syntax-missing-
|
|
227
|
+
this.warning( 'syntax-missing-semicolon', t, { code: ';' },
|
|
223
228
|
// eslint-disable-next-line max-len
|
|
224
|
-
'Add a newline before
|
|
229
|
+
'Add a $(CODE) and/or newline before the annotation assignment to indicate that it belongs to the next statement' );
|
|
225
230
|
}
|
|
226
231
|
}
|
|
227
232
|
|
|
@@ -319,7 +324,7 @@ function attachLocation( art ) {
|
|
|
319
324
|
return art;
|
|
320
325
|
}
|
|
321
326
|
|
|
322
|
-
function assignAnnotation( art, anno, prefix = ''
|
|
327
|
+
function assignAnnotation( art, anno, prefix = '' ) {
|
|
323
328
|
const { name, $flatten } = anno;
|
|
324
329
|
const { path } = name;
|
|
325
330
|
if (path.broken || !path[path.length - 1].id)
|
|
@@ -329,11 +334,9 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
|
|
|
329
334
|
if (name.variant) {
|
|
330
335
|
const variant = pathName( name.variant.path );
|
|
331
336
|
absolute = `${ prefix }${ pathname }#${ variant }`;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
'Annotation variant has been already provided' );
|
|
336
|
-
}
|
|
337
|
+
// We do not care anymore whether we get a second '#' with flattening. This
|
|
338
|
+
// can be produced via CSN and with delimited ids anyway. If backends care,
|
|
339
|
+
// they need to have their own check.
|
|
337
340
|
}
|
|
338
341
|
else if (!prefix || pathname !== '$value') {
|
|
339
342
|
absolute = `${ prefix }${ pathname }`;
|
|
@@ -343,7 +346,7 @@ function assignAnnotation( art, anno, prefix = '', iHaveVariant = false ) {
|
|
|
343
346
|
}
|
|
344
347
|
if ($flatten) {
|
|
345
348
|
for (const a of $flatten)
|
|
346
|
-
this.assignAnnotation( art, a, `${ absolute }
|
|
349
|
+
this.assignAnnotation( art, a, `${ absolute }.` );
|
|
347
350
|
}
|
|
348
351
|
else {
|
|
349
352
|
name.absolute = absolute;
|
|
@@ -682,16 +685,22 @@ function identAst( token, category, noTokenTypeCheck = false ) {
|
|
|
682
685
|
|
|
683
686
|
// only to be used in @after
|
|
684
687
|
// TODO: remove compatible stuff (A2J/checks use op: 'and'/'=')
|
|
685
|
-
function argsExpression( args,
|
|
688
|
+
function argsExpression( args, nary, location ) {
|
|
686
689
|
// console.log('AE:',args);
|
|
687
690
|
if (args.length === 1)
|
|
688
691
|
return args[0];
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
692
|
+
if (nary && args.length === 3 && args[1]?.val === nary) {
|
|
693
|
+
return this.attachLocation( {
|
|
694
|
+
op: { val: nary, location: args[1].location },
|
|
695
|
+
args: [ args[0], args[2] ],
|
|
696
|
+
location: undefined,
|
|
697
|
+
} );
|
|
698
|
+
}
|
|
699
|
+
const op = {
|
|
700
|
+
val: (nary && nary !== '=' ? 'nary' : 'ixpr'), // there is no n-ary in rule conditionTerm
|
|
701
|
+
location: this.startLocation(),
|
|
702
|
+
};
|
|
703
|
+
return this.attachLocation( { op, args, location: location && { ...location } } );
|
|
695
704
|
}
|
|
696
705
|
|
|
697
706
|
function pushXprToken( args ) {
|
|
@@ -738,7 +747,7 @@ function valuePathAst( ref ) {
|
|
|
738
747
|
// If a '-' is directly before an unsigned number, consider it part of the number;
|
|
739
748
|
// otherwise (including for '+'), represent it as extra unary prefix operator.
|
|
740
749
|
function signedExpression( args, expr ) {
|
|
741
|
-
// if (args.length !== 1) throw
|
|
750
|
+
// if (args.length !== 1) throw new CompilerAssertion()
|
|
742
751
|
const sign = args[0];
|
|
743
752
|
const nval
|
|
744
753
|
= (sign.val === '-' &&
|
|
@@ -778,7 +787,7 @@ function numberLiteral( token, sign, text = token.text ) {
|
|
|
778
787
|
if (!Number.isSafeInteger(num)) {
|
|
779
788
|
if (sign == null) {
|
|
780
789
|
this.error( 'syntax-expecting-unsigned-int', token,
|
|
781
|
-
{ '#': !text.match(
|
|
790
|
+
{ '#': !text.match(/^\d*$/) ? 'normal' : 'unsafe' } );
|
|
782
791
|
}
|
|
783
792
|
|
|
784
793
|
else if (text !== `${ num }`) {
|
|
@@ -845,26 +854,33 @@ function pushIdent( path, ident, prefix ) {
|
|
|
845
854
|
path.push( ident );
|
|
846
855
|
}
|
|
847
856
|
else {
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
file: ident.location.file,
|
|
853
|
-
line: tokenLoc.endLine, // !
|
|
854
|
-
col: tokenLoc.endCol, // !
|
|
855
|
-
endLine: ident.location.line,
|
|
856
|
-
endCol: ident.location.col,
|
|
857
|
-
};
|
|
858
|
-
this.error( 'syntax-unexpected-space', wsLocation, {}, // TODO: really Error?
|
|
859
|
-
'Expected identifier after \'@\' but found whitespace' );
|
|
860
|
-
}
|
|
861
|
-
ident.location.line = tokenLoc.line;
|
|
862
|
-
ident.location.col = tokenLoc.col;
|
|
857
|
+
const { location } = ident;
|
|
858
|
+
const prefixLoc = this.reportUnexpectedSpace( prefix, location );
|
|
859
|
+
location.line = prefixLoc.line;
|
|
860
|
+
location.col = prefixLoc.col;
|
|
863
861
|
ident.id = prefix.text + ident.id;
|
|
864
862
|
path.push( ident );
|
|
865
863
|
}
|
|
866
864
|
}
|
|
867
865
|
|
|
866
|
+
// For :param, #variant, #symbol, @(…) and @Begin and `@` inside annotation paths
|
|
867
|
+
function reportUnexpectedSpace( prefix, location = this.tokenLocation( this._input.LT(1) ) ) {
|
|
868
|
+
const prefixLoc = this.tokenLocation( prefix );
|
|
869
|
+
if (prefixLoc.endLine !== location.line ||
|
|
870
|
+
prefixLoc.endCol !== location.col) {
|
|
871
|
+
const wsLocation = {
|
|
872
|
+
file: location.file,
|
|
873
|
+
line: prefixLoc.endLine, // !
|
|
874
|
+
col: prefixLoc.endCol, // !
|
|
875
|
+
endLine: location.line,
|
|
876
|
+
endCol: location.col,
|
|
877
|
+
};
|
|
878
|
+
this.warning( 'syntax-unexpected-space', wsLocation, { op: prefix.text },
|
|
879
|
+
'Delete the whitespace after $(OP)' );
|
|
880
|
+
}
|
|
881
|
+
return prefixLoc;
|
|
882
|
+
}
|
|
883
|
+
|
|
868
884
|
// Add new definition `art` to dictionary property `env` of node `parent`.
|
|
869
885
|
// Return `art`.
|
|
870
886
|
//
|
|
@@ -1038,14 +1054,53 @@ function leftAssocBinaryOp( left, opToken, eToken, right, extraProp = 'quantifie
|
|
|
1038
1054
|
return { op, args: [ left, right ], location: left.location };
|
|
1039
1055
|
}
|
|
1040
1056
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1057
|
+
const maxCardinalityKeywords = { 1: 'one', '*': 'many' };
|
|
1058
|
+
|
|
1059
|
+
function setMaxCardinality( art, targetMax, token ) { // - val
|
|
1060
|
+
if (token)
|
|
1061
|
+
targetMax.location = this.tokenLocation( token );
|
|
1062
|
+
if (art.cardinality) {
|
|
1063
|
+
this.reportDuplicateClause( 'cardinality', targetMax, art.cardinality.targetMax,
|
|
1064
|
+
maxCardinalityKeywords );
|
|
1045
1065
|
}
|
|
1046
1066
|
else {
|
|
1047
|
-
|
|
1048
|
-
|
|
1067
|
+
art.cardinality = { targetMax, location: targetMax.location };
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const notNullKeywords = { false: 'null', true: 'not null' };
|
|
1072
|
+
|
|
1073
|
+
function setNullability( art, token1, token2 ) {
|
|
1074
|
+
const notNull = this.valueWithTokenLocation( !!token2, token1, token2 );
|
|
1075
|
+
if (art.notNull)
|
|
1076
|
+
this.reportDuplicateClause( 'notNull', art.notNull, notNull, notNullKeywords );
|
|
1077
|
+
art.notNull = notNull;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function reportDuplicateClause( prop, errorneous, chosen, keywords ) {
|
|
1081
|
+
// probably easier for message linters not to use (?:) for the message id...?
|
|
1082
|
+
const args = {
|
|
1083
|
+
'#': prop,
|
|
1084
|
+
code: keywords[chosen.val] || chosen.val,
|
|
1085
|
+
line: chosen.location.line,
|
|
1086
|
+
col: chosen.location.col,
|
|
1087
|
+
};
|
|
1088
|
+
if (errorneous.val === chosen.val)
|
|
1089
|
+
this.warning( 'syntax-duplicate-equal-clause', errorneous.location, args );
|
|
1090
|
+
else
|
|
1091
|
+
this.message( 'syntax-duplicate-clause', errorneous.location, args );
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const extensionsCode = {
|
|
1095
|
+
definitions: 'extend … with definitions',
|
|
1096
|
+
context: 'extend context',
|
|
1097
|
+
service: 'extend service',
|
|
1098
|
+
};
|
|
1099
|
+
|
|
1100
|
+
function reportUnexpectedExtension( defOnly, token ) {
|
|
1101
|
+
if (defOnly) {
|
|
1102
|
+
this.error( 'syntax-unexpected-extension', token,
|
|
1103
|
+
{ keyword: token.text, code: extensionsCode[defOnly] } );
|
|
1049
1104
|
}
|
|
1050
1105
|
}
|
|
1051
1106
|
|