@sap/cds-compiler 2.13.8 → 3.0.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +155 -1594
  2. package/bin/cdsc.js +144 -66
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +237 -122
  10. package/lib/api/options.js +17 -88
  11. package/lib/api/validate.js +12 -16
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +152 -37
  14. package/lib/base/messages.js +145 -83
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +19 -0
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +11 -32
  19. package/lib/checks/arrayOfs.js +1 -34
  20. package/lib/checks/cdsPersistence.js +1 -0
  21. package/lib/checks/elements.js +6 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/queryNoDbArtifacts.js +2 -1
  25. package/lib/checks/selectItems.js +5 -1
  26. package/lib/checks/types.js +4 -2
  27. package/lib/checks/utils.js +2 -2
  28. package/lib/checks/validator.js +4 -5
  29. package/lib/compiler/assert-consistency.js +16 -10
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +98 -9
  32. package/lib/compiler/checks.js +22 -70
  33. package/lib/compiler/define.js +61 -13
  34. package/lib/compiler/extend.js +79 -14
  35. package/lib/compiler/finalize-parse-cdl.js +46 -29
  36. package/lib/compiler/index.js +100 -37
  37. package/lib/compiler/moduleLayers.js +7 -0
  38. package/lib/compiler/populate.js +19 -18
  39. package/lib/compiler/propagator.js +7 -4
  40. package/lib/compiler/resolve.js +297 -234
  41. package/lib/compiler/shared.js +107 -102
  42. package/lib/compiler/tweak-assocs.js +16 -11
  43. package/lib/compiler/utils.js +5 -0
  44. package/lib/edm/annotations/genericTranslation.js +93 -21
  45. package/lib/edm/csn2edm.js +230 -115
  46. package/lib/edm/edm.js +305 -226
  47. package/lib/edm/edmPreprocessor.js +509 -438
  48. package/lib/edm/edmUtils.js +31 -45
  49. package/lib/gen/Dictionary.json +98 -22
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +10 -30
  52. package/lib/gen/language.tokens +105 -114
  53. package/lib/gen/languageLexer.interp +1 -34
  54. package/lib/gen/languageLexer.js +889 -1007
  55. package/lib/gen/languageLexer.tokens +95 -106
  56. package/lib/gen/languageParser.js +20786 -22199
  57. package/lib/json/csnVersion.js +10 -11
  58. package/lib/json/from-csn.js +59 -51
  59. package/lib/json/to-csn.js +10 -10
  60. package/lib/language/antlrParser.js +2 -2
  61. package/lib/language/docCommentParser.js +62 -39
  62. package/lib/language/errorStrategy.js +52 -40
  63. package/lib/language/genericAntlrParser.js +348 -229
  64. package/lib/language/language.g4 +629 -653
  65. package/lib/language/multiLineStringParser.js +14 -42
  66. package/lib/language/textUtils.js +44 -0
  67. package/lib/main.d.ts +46 -43
  68. package/lib/main.js +108 -79
  69. package/lib/model/csnRefs.js +34 -7
  70. package/lib/model/csnUtils.js +337 -332
  71. package/lib/model/enrichCsn.js +1 -0
  72. package/lib/model/revealInternalProperties.js +30 -10
  73. package/lib/model/sortViews.js +32 -31
  74. package/lib/modelCompare/compare.js +6 -6
  75. package/lib/optionProcessor.js +73 -46
  76. package/lib/render/.eslintrc.json +1 -1
  77. package/lib/render/DuplicateChecker.js +4 -7
  78. package/lib/render/manageConstraints.js +70 -2
  79. package/lib/render/toCdl.js +1042 -882
  80. package/lib/render/toHdbcds.js +195 -245
  81. package/lib/render/toRename.js +44 -22
  82. package/lib/render/toSql.js +225 -241
  83. package/lib/render/utils/common.js +145 -15
  84. package/lib/render/utils/sql.js +20 -19
  85. package/lib/sql-identifier.js +6 -0
  86. package/lib/transform/db/.eslintrc.json +4 -3
  87. package/lib/transform/db/associations.js +2 -2
  88. package/lib/transform/db/cdsPersistence.js +5 -15
  89. package/lib/transform/db/constraints.js +4 -2
  90. package/lib/transform/db/expansion.js +22 -16
  91. package/lib/transform/db/flattening.js +109 -80
  92. package/lib/transform/db/transformExists.js +7 -7
  93. package/lib/transform/db/views.js +9 -6
  94. package/lib/transform/draft/.eslintrc.json +2 -2
  95. package/lib/transform/draft/db.js +6 -6
  96. package/lib/transform/draft/odata.js +6 -7
  97. package/lib/transform/forHanaNew.js +62 -48
  98. package/lib/transform/forOdataNew.js +49 -50
  99. package/lib/transform/localized.js +31 -20
  100. package/lib/transform/odata/toFinalBaseType.js +16 -14
  101. package/lib/transform/odata/typesExposure.js +146 -198
  102. package/lib/transform/odata/utils.js +1 -38
  103. package/lib/transform/transformUtilsNew.js +67 -84
  104. package/lib/transform/translateAssocsToJoins.js +7 -3
  105. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  106. package/lib/transform/universalCsn/coreComputed.js +16 -9
  107. package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
  108. package/lib/utils/file.js +3 -3
  109. package/lib/utils/moduleResolve.js +13 -6
  110. package/lib/utils/timetrace.js +20 -21
  111. package/package.json +35 -4
  112. package/share/messages/message-explanations.json +2 -1
  113. package/share/messages/syntax-expected-integer.md +37 -0
  114. package/doc/ApiMigration.md +0 -237
  115. package/doc/CommandLineMigration.md +0 -58
  116. package/doc/ErrorMessages.md +0 -175
  117. package/doc/FioriAnnotations.md +0 -94
  118. package/doc/ODataTransformation.md +0 -273
  119. package/lib/backends.js +0 -529
  120. package/lib/fix_antlr4-8_warning.js +0 -56
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -296
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
@@ -7,7 +7,7 @@
7
7
  'use strict';
8
8
 
9
9
  const antlr4 = require('antlr4');
10
- const { ATNState } = require('antlr4/atn/ATNState');
10
+ const { ATNState } = require('antlr4/src/antlr4/atn/ATNState');
11
11
  const { dictAdd, dictAddArray } = require('../base/dictionaries');
12
12
  const locUtils = require('../base/location');
13
13
  const { parseDocComment } = require('./docCommentParser');
@@ -32,83 +32,86 @@ function _message( parser, severity, id, loc, ...args ) {
32
32
  // this.<function>(...)
33
33
  // in the actions inside the grammar.
34
34
  //
35
- function GenericAntlrParser( ...args ) {
36
- // ANTLR restriction: we cannot add parameters to the constructor.
37
- antlr4.Parser.call( this, ...args );
38
- this.buildParseTrees = false;
39
-
40
- // Common properties.
41
- // We set them here so that they are available in the prototype.
42
- // This improved performance by 25% for certain scenario tests.
43
- // Probably because there was no need to look up the prototype chain anymore.
44
- this.$adaptExpectedToken = null;
45
- this.$adaptExpectedExcludes = [ ];
46
- this.$nextTokensToken = null;
47
- this.$nextTokensContext = null;
48
-
49
- this.prepareGenericKeywords();
50
- this.options = {};
51
-
52
- return this;
53
- }
54
-
55
- // When we define this class with the ES6 `class` syntax, we get
56
- // TypeError: Class constructors cannot be invoked without 'new'
57
- // Reason: the generated ANTLR constructor calls its super constructor via
58
- // old-style `<super>.call(this,...)`, not via `super(...)`.
59
-
60
- GenericAntlrParser.prototype = Object.assign(
61
- Object.create( antlr4.Parser.prototype ), {
62
- message: function(...args) { return _message( this, 'message', ...args ); },
63
- error: function(...args) { return _message( this, 'error', ...args ); },
64
- warning: function(...args) { return _message( this, 'warning', ...args ); },
65
- info: function(...args) { return _message( this, 'info', ...args ); },
66
- attachLocation,
67
- startLocation,
68
- tokenLocation,
69
- valueWithTokenLocation,
70
- previousTokenAtLocation,
71
- combinedLocation,
72
- createDict,
73
- setDictEndLocation,
74
- surroundByParens,
75
- unaryOpForParens,
76
- leftAssocBinaryOp,
77
- classifyImplicitName,
78
- fragileAlias,
79
- identAst,
80
- functionAst,
81
- valuePathAst,
82
- signedExpression,
83
- numberLiteral,
84
- quotedLiteral,
85
- pathName,
86
- docComment,
87
- addDef,
88
- addItem,
89
- artifactForElementAnnotateOrExtend,
90
- assignProps,
91
- createPrefixOp,
92
- setOnce,
93
- setMaxCardinality,
94
- pushIdent,
95
- handleComposition,
96
- reportExpandInline,
97
- notSupportedYet,
98
- csnParseOnly,
99
- disallowElementExtension,
100
- noAssignmentInSameLine,
101
- noSemicolonHere,
102
- setLocalToken,
103
- setLocalTokenIfBefore,
104
- excludeExpected,
105
- isStraightBefore,
106
- meltKeywordToIdentifier,
107
- prepareGenericKeywords,
108
- parseMultiLineStringLiteral,
109
- constructor: GenericAntlrParser, // keep this last
110
- }
111
- );
35
+ class GenericAntlrParser extends antlr4.Parser {
36
+ constructor( ...args ) {
37
+ // ANTLR restriction: we cannot add parameters to the constructor.
38
+ super( ...args );
39
+ this.buildParseTrees = false;
40
+
41
+ // Common properties.
42
+ // We set them here so that they are available in the prototype.
43
+ // This improved performance by 25% for certain scenario tests.
44
+ // Probably because there was no need to look up the prototype chain anymore.
45
+ this.$adaptExpectedToken = null;
46
+ this.$adaptExpectedExcludes = [ ];
47
+ this.$nextTokensToken = null;
48
+ this.$nextTokensContext = null;
49
+
50
+ this.options = {};
51
+
52
+ this.genericFunctionsStack = [];
53
+ this.$genericKeywords = specialFunctions[''][1];
54
+ }
55
+ }
56
+
57
+ // TODO: Use actual methods.
58
+ Object.assign(GenericAntlrParser.prototype, {
59
+ message: function(...args) { return _message( this, 'message', ...args ); },
60
+ error: function(...args) { return _message( this, 'error', ...args ); },
61
+ warning: function(...args) { return _message( this, 'warning', ...args ); },
62
+ info: function(...args) { return _message( this, 'info', ...args ); },
63
+ attachLocation,
64
+ assignAnnotation,
65
+ startLocation,
66
+ tokenLocation,
67
+ valueWithTokenLocation,
68
+ previousTokenAtLocation,
69
+ combinedLocation,
70
+ surroundByParens,
71
+ unaryOpForParens,
72
+ leftAssocBinaryOp,
73
+ classifyImplicitName,
74
+ fragileAlias,
75
+ identAst,
76
+ functionAst,
77
+ setLastAsXpr,
78
+ xprToken,
79
+ valuePathAst,
80
+ signedExpression,
81
+ numberLiteral,
82
+ quotedLiteral,
83
+ pathName,
84
+ docComment,
85
+ addDef,
86
+ addItem,
87
+ addExtension,
88
+ createSource,
89
+ createDict,
90
+ createArray,
91
+ finalizeDictOrArray,
92
+ createPrefixOp,
93
+ setOnce,
94
+ setMaxCardinality,
95
+ pushIdent,
96
+ handleComposition,
97
+ associationInSelectItem,
98
+ reportExpandInline,
99
+ checkTypeFacet,
100
+ notSupportedYet,
101
+ csnParseOnly,
102
+ disallowElementExtension,
103
+ noAssignmentInSameLine,
104
+ noSemicolonHere,
105
+ setLocalToken,
106
+ setLocalTokenIfBefore,
107
+ setLocalTokenForId,
108
+ excludeExpected,
109
+ isStraightBefore,
110
+ meltKeywordToIdentifier,
111
+ prepareGenericKeywords,
112
+ reportErrorForGenericKeyword,
113
+ parseMultiLineStringLiteral,
114
+ });
112
115
 
113
116
  // Patterns for literal token tests and creation. The value is a map from the
114
117
  // `prefix` argument of function `quotedliteral` to the following properties:
@@ -119,26 +122,27 @@ GenericAntlrParser.prototype = Object.assign(
119
122
  // - `unexpected_char`: regular expression matching an illegal character in `value`,
120
123
  // the error location is only correct for a literal <prefix>'<value>'
121
124
  // - `literal`: the value which is used instead of `prefix` in the AST
122
- // - `normalized`: function called with argument `value`, return value is used
123
- // instead of `value` in the AST
124
- // TODO: think about laxer regexp for date/time/timestamp - normalization?
125
+ // TODO: we might do a range check (consider leap seconds, i.e. max value 60),
126
+ // but always allow Feb 29 (no leap year computation)
127
+ // TODO: make it a configurable error (syntax-invalid-literal)
128
+ // TODO: also use for CSN input
125
129
  const quotedLiteralPatterns = {
126
130
  x: {
127
- test_msg: 'A binary literal must have an even number of characters',
131
+ test_variant: 'uneven-hex',
128
132
  test_fn: (str => Number.isInteger(str.length / 2)),
129
- unexpected_msg: 'A binary literal must only contain characters 0-9, a-f and A-F',
133
+ unexpected_variant: 'invalid-hex',
130
134
  unexpected_char: /[^0-9a-f]/i,
131
135
  },
132
136
  time: {
133
- test_msg: 'Expected time\'HH:MM:SS\' where H, M and S are numbers and \':SS\' is optional',
137
+ test_variant: 'time',
134
138
  test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
135
139
  },
136
140
  date: {
137
- test_msg: 'Expected date\'YYYY-MM-DD\' where Y, M and D are numbers',
138
- test_re: /^[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,2}$/,
141
+ test_variant: 'date',
142
+ test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
139
143
  },
140
144
  timestamp: {
141
- test_msg: 'Expected timestamp\'YYYY-MM-DD HH:MM:SS.u…u\' where Y, M, D, H, S and u are numbers (optional 1-7×u)',
145
+ test_variant: 'timestamp',
142
146
  test_re: /^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}(:[0-9]{2}(\.[0-9]{1,7})?)?$/,
143
147
  },
144
148
  };
@@ -161,8 +165,8 @@ function notSupportedYet( text, ...tokens ) {
161
165
  }
162
166
 
163
167
  // Use the following function for language constructs which we (currently) do
164
- // not really compile, just use to produce a CSN for functions parseToCqn() and
165
- // parseToExpr().
168
+ // not really compile, just use to produce a CSN for functions parse.cql() and
169
+ // parse.expr().
166
170
  function csnParseOnly( text, ...tokens ) {
167
171
  if (!text || this.options.parseOnly)
168
172
  return;
@@ -216,6 +220,15 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
216
220
  ll1.type = this.constructor[tokenName];
217
221
  }
218
222
 
223
+ function setLocalTokenForId( tokenNameMap ) {
224
+ const ll1 = this.getCurrentToken();
225
+ if (ll1.type === this.constructor.Identifier || /^[a-zA-Z_]+$/.test( ll1.text )) {
226
+ const tokenName = tokenNameMap[ this._input.LT(2).text || '' ];
227
+ if (tokenName)
228
+ ll1.type = this.constructor[tokenName];
229
+ }
230
+ }
231
+
219
232
  // // Special function for rule `requiredSemi` before return $ctx
220
233
  // function braceForSemi() {
221
234
  // if (RBRACE == null)
@@ -273,24 +286,50 @@ function meltKeywordToIdentifier( exceptTrueFalseNull = false ) {
273
286
  token.type = Identifier;
274
287
  }
275
288
 
276
- function prepareGenericKeywords( pathItem ) {
277
- this.$genericKeywords = { argFull: [] };
278
- if (!pathItem)
279
- return;
280
- const func = pathItem.id && specialFunctions[pathItem.id.toUpperCase()];
281
- const spec = func && func[pathItem.args ? pathItem.args.length : 0];
282
- if (!spec)
283
- return;
284
- // currently, we only have 'argFull', i.e. a keyword which is alternative to expression
289
+ const genericTokenTypes = {
290
+ expr: 'GenericExpr',
291
+ separator: 'GenericSeparator',
292
+ intro: 'GenericIntro',
293
+ };
294
+
295
+ function prepareGenericKeywords( pathItem, expected = null) {
296
+ const length = pathItem?.args?.length || 0;
297
+ const argPos = (expected ? length -1 : length);
298
+ const func = pathItem?.id && specialFunctions[pathItem.id.toUpperCase()];
299
+ const spec = func && func[argPos] || specialFunctions[''][argPos ? 1 : 0];
300
+ this.$genericKeywords = spec;
301
+ // currently, we only have 'TODO', i.e. a keyword which is alternative to expression
285
302
  // TODO: If not just at the beginning, we need a stack for $genericKeywords,
286
303
  // as we can have nested special functions
287
- this.$genericKeywords.argFull = Object.keys( spec );
288
304
  // @ts-ignore
289
305
  const token = this.getCurrentToken() || { text: '' };
290
- if (spec[token.text.toUpperCase()] === 'argFull') {
291
- // @ts-ignore
292
- token.type = this.constructor.GenericArgFull;
306
+ const text = token.text.toUpperCase();
307
+ let generic = spec[text];
308
+ // console.log('PGK:',token.text,generic,expected,spec,func,argPos)
309
+ if (expected) { // 'separator' or 'expr' (after 'separator')
310
+ if (generic !== expected)
311
+ return;
312
+ }
313
+ else if (!generic || generic === 'separator') {
314
+ // Mismatch at beginning (or just an expression): keep token type
315
+ // (if not expression, issue error and consider the token to be an
316
+ // expression replacement, like ALL)
317
+ return;
318
+ }
319
+ else if (generic === 'expr' && spec.intro && spec.intro.includes( text )) {
320
+ // token is both an intro and an expression, like LEADING for TRIM
321
+ const next = this._input.LT(2).text;
322
+ if (!next || // followed by EOF -> consider it to be 'intro', better for CC
323
+ next !== ',' && next !== ')' && spec[next.toUpperCase()] !== 'separator')
324
+ generic = 'intro'; // is intro if next token is not separator, not ',', ')'
293
325
  }
326
+ // @ts-ignore
327
+ token.type = this.constructor[genericTokenTypes[generic]];
328
+ }
329
+ // To be called before having matched ( HideAlternatives | … )
330
+ function reportErrorForGenericKeyword() {
331
+ this._errHandler.reportUnwantedToken( this );
332
+ //this._errHandler.reportInputMismatch( this, { offending: this._input.LT(1) }, null );
294
333
  }
295
334
 
296
335
  // Attach location matched by current rule to node `art`. If a location is
@@ -308,6 +347,52 @@ function attachLocation( art ) {
308
347
  return art;
309
348
  }
310
349
 
350
+ function assignAnnotation( art, anno, prefix = '', iHaveVariant ) {
351
+ const { name, $flatten } = anno;
352
+ const { path } = name;
353
+ if (path.broken || !path[path.length - 1].id)
354
+ return;
355
+ const pathname = pathName( path );
356
+ let absolute = '';
357
+ if (name.variant) {
358
+ absolute = `${ prefix }${ pathname }#${ name.variant.id }`;
359
+ if (iHaveVariant) { // TODO: do we really care in the parser / core compiler?
360
+ this.error( 'anno-duplicate-variant', [ name.variant.location ],
361
+ {}, // TODO: params
362
+ 'Annotation variant has been already provided' );
363
+ }
364
+ }
365
+ else if (!prefix || pathname !== '$value') {
366
+ absolute = `${ prefix }${ pathname }`;
367
+ }
368
+ else {
369
+ absolute = prefix.slice( 0, -1 );
370
+ }
371
+ if ($flatten) {
372
+ for (const a of $flatten)
373
+ this.assignAnnotation( art, a, `${ absolute }.`, iHaveVariant || name.variant);
374
+ }
375
+ else {
376
+ name.absolute = absolute;
377
+ const prop = '@' + absolute;
378
+ const old = art[prop];
379
+ if (old && old.$inferred)
380
+ art[prop] = anno;
381
+ else
382
+ dictAddArray( art, prop, anno, (n, location, a) => {
383
+ this.error( 'syntax-duplicate-anno', [ location ], { anno: n },
384
+ 'Duplicate assignment with $(ANNO)' );
385
+ a.$errorReported = true; // do not report again later as anno-duplicate-xyz
386
+ } );
387
+ }
388
+ if (!prefix) { // set deprecated $annnotations for cds-lsp
389
+ if (!art.$annotations)
390
+ art.$annotations = [];
391
+ const location = locUtils.combinedLocation( anno.name, anno );
392
+ art.$annotations.push( { value: anno, location } );
393
+ }
394
+ }
395
+
311
396
  /**
312
397
  * Return start location of `token`, or the first token matched by the current
313
398
  * rule if `token` is undefined
@@ -407,20 +492,6 @@ function combinedLocation( start, end ) {
407
492
  return locUtils.combinedLocation( start, end );
408
493
  }
409
494
 
410
- function createDict( location = null ) {
411
- const dict = Object.create(null);
412
- dict[$location] = location || this.startLocation( this._input.LT(-1) );
413
- return dict;
414
- }
415
-
416
- function setDictEndLocation( dict ) {
417
- const stop = this._input.LT(-1);
418
- Object.assign( dict[$location], {
419
- endLine: stop.line,
420
- endCol: stop.stop - stop.start + stop.column + 2,
421
- } );
422
- }
423
-
424
495
  function surroundByParens( expr, open, close, asQuery = false ) {
425
496
  if (!expr)
426
497
  return expr;
@@ -486,10 +557,11 @@ function fragileAlias( ast, safe = false ) {
486
557
  }
487
558
 
488
559
  // Return AST for identifier token `token`. Also check that identifer is not empty.
489
- function identAst( token, category ) {
560
+ function identAst( token, category, noTokenTypeCheck = false ) {
490
561
  token.isIdentifier = category;
491
562
  let id = token.text;
492
- if (token.type !== this.constructor.Identifier && !/^[a-zA-Z]+$/.test( id ))
563
+ if (!noTokenTypeCheck &&
564
+ token.type !== this.constructor.Identifier && !/^[a-zA-Z_]+$/.test( id ))
493
565
  id = '';
494
566
  if (token.text[0] === '!') {
495
567
  id = id.slice( 2, -1 ).replace( /]]/g, ']' );
@@ -516,11 +588,11 @@ function identAst( token, category ) {
516
588
  return { id, $delimited: true, location: this.tokenLocation( token ) };
517
589
  }
518
590
 
519
- function functionAst( token, xprToken ) {
591
+ function functionAst( token, xpr ) { // only for special function TRIM and EXTRACT
520
592
  // TODO: XSN func cleanup
521
593
  const location = this.tokenLocation( token );
522
- const args = xprToken
523
- ? [ { op: { location, val: 'xpr' }, args: [], location: this.tokenLocation( xprToken ) } ]
594
+ const args = xpr
595
+ ? [ { op: { location, val: 'ixpr' }, args: [], location: this.tokenLocation( xpr ) } ]
524
596
  : [];
525
597
  return {
526
598
  op: { location, val: 'call' },
@@ -530,6 +602,21 @@ function functionAst( token, xprToken ) {
530
602
  };
531
603
  }
532
604
 
605
+ function setLastAsXpr( args ) {
606
+ const pos = args.length - 1;
607
+ const last = args[pos];
608
+ if (!last || last?.op?.val === 'ixpr') // actuall an internal xpr, which is always flattened
609
+ return last;
610
+ const location = { ...last.location };
611
+ args[pos] = { op: { location, val: 'ixpr' }, args: [ last ], location };
612
+ return args[pos].args;
613
+ }
614
+
615
+ function xprToken() {
616
+ const token = this._input.LT(-1);
617
+ return { location: this.tokenLocation( token ), val: token.text, literal: 'token' }
618
+ }
619
+
533
620
  function valuePathAst( ref ) {
534
621
  // TODO: XSN representation of functions is a bit strange - rework if methods
535
622
  // are introduced
@@ -594,11 +681,11 @@ function numberLiteral( token, sign, text = token.text ) {
594
681
  location.endCol = endCol;
595
682
  text = sign.text + text;
596
683
  }
684
+
597
685
  const num = Number.parseFloat( text || '0' ); // not Number.parseInt() !
598
686
  if (!Number.isSafeInteger(num)) {
599
687
  if (sign == null) {
600
- this.error( 'syntax-no-integer', token, {},
601
- 'An integer number is expected here' );
688
+ this.error( 'syntax-expected-integer', token, { '#': !text.match(/^[0-9]*$/) ? 'normal' : 'unsafe'} );
602
689
  }
603
690
  else if (text !== `${num}`) {
604
691
  return { literal: 'number', val: text, location };
@@ -628,21 +715,20 @@ function quotedLiteral( token, literal ) {
628
715
  literal = token.text.slice( 0, pos - 1 ).toLowerCase();
629
716
  const p = quotedLiteralPatterns[literal] || {};
630
717
 
631
- // TODO: make tests available for CSN parser
632
718
  if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
633
719
  !this.options.parseOnly)
634
- this.error( null, location, p.test_msg ); // TODO: message id
720
+ this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
635
721
 
636
722
  if (p.unexpected_char) {
637
723
  const idx = val.search(p.unexpected_char);
638
724
  if (~idx) {
639
- this.error( null, { // TODO: message id
725
+ this.warning( 'syntax-invalid-literal', {
640
726
  file: location.file,
641
727
  line: location.line,
642
728
  endLine: location.line,
643
729
  col: atChar(idx),
644
730
  endCol: atChar( idx + (val[idx] === '\'' ? 2 : 1) ),
645
- }, p.unexpected_msg );
731
+ }, { '#': p.unexpected_variant } );
646
732
  }
647
733
  }
648
734
  return {
@@ -689,61 +775,62 @@ function pushIdent( path, ident, prefix ) {
689
775
  }
690
776
  }
691
777
 
692
- // Add new definition to dictionary property `env` of node `parent` and return
693
- // that definition. Also attach the following properties to the new definition:
694
- // - `name`: argument `name`, which is used as key in the dictionary
695
- // - `kind`: argument `kind` if that is truthy
696
- // - `location`: argument `location` or the start location of source matched by
697
- // current rule
698
- // - properties in argument `props` which are no empty (undefined, null, {},
699
- // []), ANTLR tokens are replaced by their locations
778
+ // Add new definition `art` to dictionary property `env` of node `parent`.
779
+ // Return `art`.
700
780
  //
701
- // Hack: if argument `location` is exactly `true`, do not set `location`
702
- // (except if part of `props`), but also include the empty properties of
703
- // `props`.
704
- function addDef( parent, env, kind, name, annos, props, location ) {
781
+ // If argument `kind` is provided, set `art.kind` to that value.
782
+ // If argument `name` is provided, set `art.name`:
783
+ // - if `name` is an array, the name consist of the ID of the last path item
784
+ // (for elements via columns, foreign keys, table aliases)
785
+ // - if `name` is an object, the name consist of the IDs of all path items
786
+ // (for main artifact definitions)
787
+ function addDef( art, parent, env, kind, name ) {
705
788
  if (Array.isArray(name)) {
706
789
  // XSN TODO: clearly say: definitions have name.path, members have name.id
707
790
  const last = name.length && name[name.length - 1];
708
791
  if (last && last.id) { // // A.B.C -> 'C'
709
- name = {
792
+ art.name = {
710
793
  id: last.id, location: last.location, $inferred: 'as',
711
794
  };
712
795
  }
713
796
  }
714
- else if (name && name.id == null) {
715
- name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
797
+ else if (name) {
798
+ art.name = name;
799
+ if (name.id == null)
800
+ name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
801
+ // TODO: get rid of setting `id`, only use for named values in structs
716
802
  }
717
- const art = this.assignProps( { name }, annos, props, location );
803
+
718
804
  if (kind)
719
805
  art.kind = kind;
720
- if (!parent[env]) // TODO: dump with --test-mode, env !== 'artifacts'
721
- parent[env] = env === 'args' ? Object.create(null) : this.createDict( { ...location } );
722
806
  if (!art.name || art.name.id == null) {
723
807
  // no id was parsed, but with error recovery: no further error
724
808
  // TODO: add to parent[env]['']
725
809
  // which could be tested in name search (then no undefined-ref error)
810
+ // console.log( kind+': !!', art, parent, env, name )
726
811
  return art;
727
812
  }
728
- else if (env === 'artifacts' || env === 'vocabularies') {
813
+ // console.log( kind+':', art, parent, env, name )
814
+
815
+ if (env === 'artifacts' || env === 'vocabularies') {
729
816
  dictAddArray( parent[env], art.name.id, art );
730
817
  }
731
818
  else if (kind || this.options.parseOnly) {
732
819
  dictAdd( parent[env], art.name.id, art );
733
820
  }
734
821
  else {
735
- dictAdd( parent[env], art.name.id, art, ( name, loc ) => {
822
+ dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
736
823
  // do not use function(), otherwise `this` is wrong:
737
824
  if (kind === 0) {
738
- this.error( 'duplicate-argument', loc, { name },
825
+ this.error( 'duplicate-argument', loc, { name: duplicateName },
739
826
  'Duplicate value for parameter $(NAME)' );
740
827
  }
741
828
  else if (kind === '') {
742
- this.error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
829
+ this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
743
830
  'Duplicate $(NAME) in the $(KEYWORD) clause' );
744
831
  }
745
832
  else {
746
- this.error( 'duplicate-prop', loc, { name },
833
+ this.error( 'duplicate-prop', loc, { name: duplicateName },
747
834
  'Duplicate value for structure property $(NAME)' );
748
835
  }
749
836
  } );
@@ -751,85 +838,78 @@ function addDef( parent, env, kind, name, annos, props, location ) {
751
838
  return art;
752
839
  }
753
840
 
754
- // Add new definition to array property `env` of node `parent` and return
755
- // that definition. Also attach the following properties to the new definition:
756
- // - `kind`: argument `kind` if that is truthy
757
- // - `location`: argument `location` or the start location of source matched by
758
- // current rule
759
- // - properties in argument `props` which are no empty (undefined, null, {},
760
- // []); ANTLR tokens are replaced by their locations
761
- //
762
- // Hack: if argument `location` is exactly `true`, do not set `location`
763
- // (except if part of `props`), but also include the empty properties of
764
- // `props`.
765
- function addItem( parent, env, kind, annos, props, location ) {
766
- const art = this.assignProps( {}, annos, props, location );
767
- if (kind)
768
- art.kind = kind;
769
- if (!env)
770
- parent.push( art );
771
- else if (!parent[env])
772
- parent[env] = [ art ];
773
- else
774
- parent[env].push( art );
841
+ // Add new definition `art` to array property `env` of node `parent`.
842
+ // Also set `kind`. Returns `art`.
843
+ function addItem( art, parent, env, kind ) {
844
+ art.kind = kind;
845
+ parent[env].push( art );
775
846
  return art;
776
847
  }
777
-
778
848
  /**
779
- * For `annotate/extend E:elem.sub`, create the `elements` structure
780
- * that can be used by the core compiler to annotate/extend elements.
849
+ * Add `annotate/extend Main.Artifact:elem.sub` to `‹xsn›.extensions`:
850
+ * - the array item is an extend/annotate for `Main.Artifact`,
851
+ * - for each path item in `elem.sub`, we add an `elements` property containing
852
+ * one extend/annotate for the corresponding element
853
+ * - The deepest extend/annotate is the object which is to be extended
781
854
  *
782
- * @param {string} kind Either `annotate` or `extend`
783
- * @param {object} artifact Main artifact that shall have `elements`.
784
- * @param {XSN.Path} elementPath Path as returned by `simplePath` token.
785
- * @param {object[]} annos Existing annotations that shall be added to the _last_ path step.
786
- * @param {XSN.Location} artifactLocation Start location of the `annotate` statement.
787
- * @returns {object} Deepest element
788
- */
789
- function artifactForElementAnnotateOrExtend(kind, artifact, elementPath, annos, artifactLocation ) {
790
- if (!Array.isArray(elementPath) || elementPath.broken || elementPath.length < 1)
791
- return artifact;
792
-
793
- for (const seg of elementPath.slice(0, -1)) {
794
- artifact = this.addDef( artifact, 'elements', kind,
795
- { path: [seg], location: seg.location }, null, {}, artifactLocation );
855
+ * @param {object} ext The object containing the location and annotations for the extension.
856
+ * @param {object} parent The parent containing the `extensions` property, i.e. the source.
857
+ * @param {string} kind Either `annotate` or `extend`.
858
+ * @param {object} artName The "name object" for `Main.Artifact`.
859
+ * @param {XSN.Path} elemPath Path as returned by `simplePath` rule.
860
+ */
861
+ function addExtension( ext, parent, kind, artName, elemPath ) {
862
+ const { location } = ext;
863
+ if (!Array.isArray( elemPath ) || !elemPath.length || elemPath.broken) {
864
+ ext.name = artName;
865
+ this.addItem( ext, parent, 'extensions', kind );
866
+ return;
867
+ }
868
+ // Note: the element extensions share a common `location`, also with the
869
+ // extension of the main artifact; its end location will usually set later
870
+ parent = this.addItem( { name: artName, location }, parent, 'extensions', kind );
871
+
872
+ const last = elemPath[elemPath.length - 1];
873
+ for (const seg of elemPath) {
874
+ parent.elements = Object.create(null); // no dict location → no createDict()
875
+ parent = this.addDef( (seg === last ? ext : { location }),
876
+ parent, 'elements', kind, seg );
796
877
  }
797
- const last = elementPath[elementPath.length - 1];
798
- artifact = this.addDef( artifact, 'elements', kind,
799
- { path: [ last ], location: last.location }, annos, {}, artifactLocation );
800
- return artifact;
801
878
  }
802
879
 
803
- /** Assign all non-empty (undefined, null, {}, []) properties in argument
804
- * `props` and argument `annos` as property `$annotations` to `target`
805
- * and return it. Hack: if argument `annos` is exactly `true`, return
806
- * `Object.assign( target, props )`, for rule `namedValue`. ANTLR tokens are
807
- * replaced by their locations.
808
- *
809
- * @param {any} target
810
- * @param {any[]|true} [annos=[]]
811
- * @param {any} [props]
812
- * @param {any} [location]
813
- */
814
- function assignProps( target, annos = [], props = null, location = null) {
815
- if (annos === true)
816
- return Object.assign( target, props );
817
- target.location = location || this.startLocation( this._ctx.start );
818
- // Object.assign without "empty" elements/properties and with mappings:
819
- // - token instanceof antlr4.CommonToken => location of token
820
- for (const key in props) {
821
- let val = props[key];
822
- if (val instanceof antlr4.CommonToken)
823
- val = this.valueWithTokenLocation( true, val);
824
- // only copy properties which are not undefined, null, {} or []
825
- if (val != null &&
826
- (typeof val !== 'object' ||
827
- (Array.isArray(val) ? val.length : Object.getOwnPropertyNames(val).length) ) )
828
- target[key] = val;
829
- }
830
- if (annos)
831
- target.$annotations = annos;
832
- return target;
880
+ // must be in action directly after having parsed '{' or '(`
881
+ function createDict() {
882
+ const dict = Object.create(null);
883
+ dict[$location] = this.startLocation( this._input.LT(-1) );
884
+ return dict;
885
+ }
886
+
887
+ // must be in action directly after having parsed '[' or '(` or `{`
888
+ function createArray() {
889
+ const array = [];
890
+ array[$location] = this.startLocation( this._input.LT(-1) );
891
+ return array;
892
+ }
893
+
894
+ // must be in action directly after having parsed '}' or ')`
895
+ function finalizeDictOrArray( dict ) {
896
+ const loc = dict[$location];
897
+ if (!loc)
898
+ return;
899
+ const stop = this._input.LT(-1);
900
+ loc.endLine = stop.line;
901
+ loc.endCol = stop.stop - stop.start + stop.column + 2;
902
+ }
903
+
904
+ function createSource() {
905
+ return {
906
+ kind: 'source',
907
+ usings: [],
908
+ dependencies: [],
909
+ artifacts: Object.create(null),
910
+ // vocabularies: Object.create(null),
911
+ extensions: [],
912
+ };
833
913
  }
834
914
 
835
915
  // Create AST node for prefix operator `op` and arguments `args`
@@ -877,14 +957,12 @@ function setOnce( target, prop, value, ...tokens ) {
877
957
  target[prop] = value;
878
958
  }
879
959
 
880
- function setMaxCardinality( art, token, max, inferred ) {
960
+ function setMaxCardinality( art, token, max ) {
881
961
  const location = this.tokenLocation( token );
882
962
  if (!art.cardinality) {
883
963
  art.cardinality = { targetMax: Object.assign( { location }, max ), location };
884
- if (inferred)
885
- art.cardinality.$inferred = inferred;
886
964
  }
887
- else if (!inferred) {
965
+ else {
888
966
  this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
889
967
  'The target cardinality has already been specified - ignored $(KEYWORD)' );
890
968
  }
@@ -902,6 +980,31 @@ function handleComposition( cardinality, isComposition ) {
902
980
  this.excludeExpected( [ [ "'}'", 'COMPOSITIONofBRACE' ], brace1, ...manyOne ] );
903
981
  }
904
982
 
983
+ function associationInSelectItem( art ) {
984
+ const isPath = art.value.path && art.value.path.length
985
+ const isIdentifier = isPath && art.value.path.length === 1;
986
+ if (isIdentifier) {
987
+ if (!art.name) {
988
+ art.name = art.value.path[0];
989
+ } else {
990
+ // Use alias if provided, i.e. ignore art.value.path.
991
+ this.error( 'query-unexpected-alias', art.name.location, {},
992
+ 'Unexpected alias for association' );
993
+ }
994
+ delete art.value;
995
+ } else {
996
+ const loc = isPath ? art.value.path[1].location : art.value.location;
997
+ // If neither path nor alias are present, `query-req-name` is emitted in `populate.js`.
998
+ if (isPath || art.name) {
999
+ this.error( 'query-expected-identifier', loc, { '#': 'assoc' } );
1000
+ if (isPath) {
1001
+ art.name = art.value.path[art.value.path.length - 1];
1002
+ }
1003
+ delete art.value;
1004
+ }
1005
+ }
1006
+ }
1007
+
905
1008
  function reportExpandInline( clauseName ) {
906
1009
  let token = this.getCurrentToken();
907
1010
  // improve error location when using "inline" `.{…}` after ref (arguments and
@@ -913,6 +1016,22 @@ function reportExpandInline( clauseName ) {
913
1016
  'Unexpected nested $(PROP), can only be used after a reference' );
914
1017
  }
915
1018
 
916
- module.exports = {
917
- genericAntlrParser: GenericAntlrParser,
918
- };
1019
+ function checkTypeFacet( art, argIdent ) {
1020
+ const id = argIdent.id;
1021
+ if (id === 'length' || id === 'scale' || id === 'precision' || id === 'srid') {
1022
+ if (art[id] !== undefined) {
1023
+ this.error( 'syntax-duplicate-argument', argIdent.location,
1024
+ { '#': 'duplicate', code: id } );
1025
+ this.error( 'syntax-duplicate-argument', art[id].location,
1026
+ { '#': 'duplicate', code: id } );
1027
+ }
1028
+ return true;
1029
+
1030
+ } else {
1031
+ this.error( 'syntax-duplicate-argument', argIdent.location,
1032
+ { '#': 'unknown', code: id } );
1033
+ return false;
1034
+ }
1035
+ }
1036
+
1037
+ module.exports = GenericAntlrParser;