@sap/cds-compiler 2.15.2 → 3.0.2

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