@sap/cds-compiler 2.15.4 → 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 (105) hide show
  1. package/CHANGELOG.md +33 -1590
  2. package/bin/cdsc.js +36 -33
  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 +220 -103
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +216 -109
  13. package/lib/base/message-registry.js +60 -20
  14. package/lib/base/messages.js +65 -24
  15. package/lib/base/model.js +44 -2
  16. package/lib/checks/actionsFunctions.js +7 -5
  17. package/lib/checks/annotationsOData.js +1 -1
  18. package/lib/checks/cdsPersistence.js +1 -0
  19. package/lib/checks/elements.js +6 -6
  20. package/lib/checks/invalidTarget.js +1 -1
  21. package/lib/checks/nonexpandableStructured.js +1 -1
  22. package/lib/checks/queryNoDbArtifacts.js +2 -1
  23. package/lib/checks/selectItems.js +5 -1
  24. package/lib/checks/types.js +4 -2
  25. package/lib/checks/utils.js +2 -2
  26. package/lib/checks/validator.js +2 -1
  27. package/lib/compiler/assert-consistency.js +15 -10
  28. package/lib/compiler/builtins.js +87 -9
  29. package/lib/compiler/define.js +2 -2
  30. package/lib/compiler/extend.js +59 -11
  31. package/lib/compiler/finalize-parse-cdl.js +20 -9
  32. package/lib/compiler/index.js +25 -11
  33. package/lib/compiler/moduleLayers.js +7 -0
  34. package/lib/compiler/populate.js +13 -13
  35. package/lib/compiler/propagator.js +3 -3
  36. package/lib/compiler/resolve.js +193 -218
  37. package/lib/compiler/shared.js +47 -76
  38. package/lib/compiler/tweak-assocs.js +9 -10
  39. package/lib/compiler/utils.js +5 -0
  40. package/lib/edm/csn2edm.js +18 -21
  41. package/lib/edm/edmPreprocessor.js +25 -30
  42. package/lib/edm/edmUtils.js +10 -24
  43. package/lib/gen/language.checksum +1 -1
  44. package/lib/gen/language.interp +8 -30
  45. package/lib/gen/language.tokens +105 -114
  46. package/lib/gen/languageLexer.interp +1 -34
  47. package/lib/gen/languageLexer.js +889 -1007
  48. package/lib/gen/languageLexer.tokens +95 -106
  49. package/lib/gen/languageParser.js +20632 -22313
  50. package/lib/json/from-csn.js +56 -49
  51. package/lib/json/to-csn.js +10 -8
  52. package/lib/language/antlrParser.js +2 -2
  53. package/lib/language/docCommentParser.js +61 -38
  54. package/lib/language/errorStrategy.js +52 -40
  55. package/lib/language/genericAntlrParser.js +303 -229
  56. package/lib/language/language.g4 +573 -629
  57. package/lib/language/multiLineStringParser.js +14 -42
  58. package/lib/language/textUtils.js +44 -0
  59. package/lib/main.d.ts +27 -42
  60. package/lib/main.js +104 -81
  61. package/lib/model/csnRefs.js +1 -1
  62. package/lib/model/csnUtils.js +170 -283
  63. package/lib/model/revealInternalProperties.js +28 -8
  64. package/lib/model/sortViews.js +32 -31
  65. package/lib/optionProcessor.js +12 -21
  66. package/lib/render/.eslintrc.json +1 -1
  67. package/lib/render/DuplicateChecker.js +4 -7
  68. package/lib/render/manageConstraints.js +70 -2
  69. package/lib/render/toCdl.js +334 -339
  70. package/lib/render/toHdbcds.js +19 -15
  71. package/lib/render/toRename.js +44 -22
  72. package/lib/render/toSql.js +53 -51
  73. package/lib/render/utils/common.js +15 -1
  74. package/lib/render/utils/sql.js +20 -19
  75. package/lib/sql-identifier.js +6 -0
  76. package/lib/transform/db/.eslintrc.json +3 -2
  77. package/lib/transform/db/cdsPersistence.js +5 -15
  78. package/lib/transform/db/constraints.js +1 -1
  79. package/lib/transform/db/expansion.js +7 -6
  80. package/lib/transform/db/flattening.js +18 -19
  81. package/lib/transform/db/views.js +3 -3
  82. package/lib/transform/draft/.eslintrc.json +2 -2
  83. package/lib/transform/draft/db.js +6 -6
  84. package/lib/transform/draft/odata.js +6 -7
  85. package/lib/transform/forHanaNew.js +19 -22
  86. package/lib/transform/forOdataNew.js +10 -12
  87. package/lib/transform/localized.js +22 -16
  88. package/lib/transform/odata/toFinalBaseType.js +10 -10
  89. package/lib/transform/odata/typesExposure.js +3 -3
  90. package/lib/transform/odata/utils.js +1 -38
  91. package/lib/transform/transformUtilsNew.js +63 -77
  92. package/lib/transform/translateAssocsToJoins.js +2 -2
  93. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  94. package/lib/transform/universalCsn/coreComputed.js +11 -6
  95. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  96. package/lib/utils/file.js +3 -3
  97. package/lib/utils/timetrace.js +20 -21
  98. package/package.json +35 -4
  99. package/doc/ApiMigration.md +0 -237
  100. package/doc/CommandLineMigration.md +0 -58
  101. package/doc/ErrorMessages.md +0 -175
  102. package/doc/FioriAnnotations.md +0 -94
  103. package/doc/ODataTransformation.md +0 -273
  104. package/lib/backends.js +0 -529
  105. package/lib/fix_antlr4-8_warning.js +0 -56
@@ -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,85 +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
- 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
- );
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
+ });
114
115
 
115
116
  // Patterns for literal token tests and creation. The value is a map from the
116
117
  // `prefix` argument of function `quotedliteral` to the following properties:
@@ -121,26 +122,27 @@ GenericAntlrParser.prototype = Object.assign(
121
122
  // - `unexpected_char`: regular expression matching an illegal character in `value`,
122
123
  // the error location is only correct for a literal <prefix>'<value>'
123
124
  // - `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?
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
127
129
  const quotedLiteralPatterns = {
128
130
  x: {
129
- test_msg: 'A binary literal must have an even number of characters',
131
+ test_variant: 'uneven-hex',
130
132
  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',
133
+ unexpected_variant: 'invalid-hex',
132
134
  unexpected_char: /[^0-9a-f]/i,
133
135
  },
134
136
  time: {
135
- test_msg: 'Expected time\'HH:MM:SS\' where H, M and S are numbers and \':SS\' is optional',
137
+ test_variant: 'time',
136
138
  test_re: /^[0-9]{1,2}:[0-9]{1,2}(:[0-9]{1,2})?$/,
137
139
  },
138
140
  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
+ test_variant: 'date',
142
+ test_re: /^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/,
141
143
  },
142
144
  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)',
145
+ test_variant: 'timestamp',
144
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})?)?$/,
145
147
  },
146
148
  };
@@ -163,8 +165,8 @@ function notSupportedYet( text, ...tokens ) {
163
165
  }
164
166
 
165
167
  // 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().
168
+ // not really compile, just use to produce a CSN for functions parse.cql() and
169
+ // parse.expr().
168
170
  function csnParseOnly( text, ...tokens ) {
169
171
  if (!text || this.options.parseOnly)
170
172
  return;
@@ -218,6 +220,15 @@ function setLocalTokenIfBefore( string, tokenName, before, inSameLine ) {
218
220
  ll1.type = this.constructor[tokenName];
219
221
  }
220
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
+
221
232
  // // Special function for rule `requiredSemi` before return $ctx
222
233
  // function braceForSemi() {
223
234
  // if (RBRACE == null)
@@ -275,24 +286,50 @@ function meltKeywordToIdentifier( exceptTrueFalseNull = false ) {
275
286
  token.type = Identifier;
276
287
  }
277
288
 
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
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
287
302
  // TODO: If not just at the beginning, we need a stack for $genericKeywords,
288
303
  // as we can have nested special functions
289
- this.$genericKeywords.argFull = Object.keys( spec );
290
304
  // @ts-ignore
291
305
  const token = this.getCurrentToken() || { text: '' };
292
- if (spec[token.text.toUpperCase()] === 'argFull') {
293
- // @ts-ignore
294
- 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 ',', ')'
295
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 );
296
333
  }
297
334
 
298
335
  // Attach location matched by current rule to node `art`. If a location is
@@ -310,6 +347,52 @@ function attachLocation( art ) {
310
347
  return art;
311
348
  }
312
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
+
313
396
  /**
314
397
  * Return start location of `token`, or the first token matched by the current
315
398
  * rule if `token` is undefined
@@ -409,20 +492,6 @@ function combinedLocation( start, end ) {
409
492
  return locUtils.combinedLocation( start, end );
410
493
  }
411
494
 
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
495
  function surroundByParens( expr, open, close, asQuery = false ) {
427
496
  if (!expr)
428
497
  return expr;
@@ -488,10 +557,11 @@ function fragileAlias( ast, safe = false ) {
488
557
  }
489
558
 
490
559
  // Return AST for identifier token `token`. Also check that identifer is not empty.
491
- function identAst( token, category ) {
560
+ function identAst( token, category, noTokenTypeCheck = false ) {
492
561
  token.isIdentifier = category;
493
562
  let id = token.text;
494
- 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 ))
495
565
  id = '';
496
566
  if (token.text[0] === '!') {
497
567
  id = id.slice( 2, -1 ).replace( /]]/g, ']' );
@@ -518,11 +588,11 @@ function identAst( token, category ) {
518
588
  return { id, $delimited: true, location: this.tokenLocation( token ) };
519
589
  }
520
590
 
521
- function functionAst( token, xprToken ) {
591
+ function functionAst( token, xpr ) { // only for special function TRIM and EXTRACT
522
592
  // TODO: XSN func cleanup
523
593
  const location = this.tokenLocation( token );
524
- const args = xprToken
525
- ? [ { op: { location, val: 'xpr' }, args: [], location: this.tokenLocation( xprToken ) } ]
594
+ const args = xpr
595
+ ? [ { op: { location, val: 'ixpr' }, args: [], location: this.tokenLocation( xpr ) } ]
526
596
  : [];
527
597
  return {
528
598
  op: { location, val: 'call' },
@@ -532,6 +602,21 @@ function functionAst( token, xprToken ) {
532
602
  };
533
603
  }
534
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
+
535
620
  function valuePathAst( ref ) {
536
621
  // TODO: XSN representation of functions is a bit strange - rework if methods
537
622
  // are introduced
@@ -630,21 +715,20 @@ function quotedLiteral( token, literal ) {
630
715
  literal = token.text.slice( 0, pos - 1 ).toLowerCase();
631
716
  const p = quotedLiteralPatterns[literal] || {};
632
717
 
633
- // TODO: make tests available for CSN parser
634
718
  if ((p.test_fn && !p.test_fn(val) || p.test_re && !p.test_re.test(val)) &&
635
719
  !this.options.parseOnly)
636
- this.error( null, location, p.test_msg ); // TODO: message id
720
+ this.warning( 'syntax-invalid-literal', location, { '#': p.test_variant } );
637
721
 
638
722
  if (p.unexpected_char) {
639
723
  const idx = val.search(p.unexpected_char);
640
724
  if (~idx) {
641
- this.error( null, { // TODO: message id
725
+ this.warning( 'syntax-invalid-literal', {
642
726
  file: location.file,
643
727
  line: location.line,
644
728
  endLine: location.line,
645
729
  col: atChar(idx),
646
730
  endCol: atChar( idx + (val[idx] === '\'' ? 2 : 1) ),
647
- }, p.unexpected_msg );
731
+ }, { '#': p.unexpected_variant } );
648
732
  }
649
733
  }
650
734
  return {
@@ -691,61 +775,62 @@ function pushIdent( path, ident, prefix ) {
691
775
  }
692
776
  }
693
777
 
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
778
+ // Add new definition `art` to dictionary property `env` of node `parent`.
779
+ // Return `art`.
702
780
  //
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 ) {
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 ) {
707
788
  if (Array.isArray(name)) {
708
789
  // XSN TODO: clearly say: definitions have name.path, members have name.id
709
790
  const last = name.length && name[name.length - 1];
710
791
  if (last && last.id) { // // A.B.C -> 'C'
711
- name = {
792
+ art.name = {
712
793
  id: last.id, location: last.location, $inferred: 'as',
713
794
  };
714
795
  }
715
796
  }
716
- else if (name && name.id == null) {
717
- 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
718
802
  }
719
- const art = this.assignProps( { name }, annos, props, location );
803
+
720
804
  if (kind)
721
805
  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
806
  if (!art.name || art.name.id == null) {
725
807
  // no id was parsed, but with error recovery: no further error
726
808
  // TODO: add to parent[env]['']
727
809
  // which could be tested in name search (then no undefined-ref error)
810
+ // console.log( kind+': !!', art, parent, env, name )
728
811
  return art;
729
812
  }
730
- else if (env === 'artifacts' || env === 'vocabularies') {
813
+ // console.log( kind+':', art, parent, env, name )
814
+
815
+ if (env === 'artifacts' || env === 'vocabularies') {
731
816
  dictAddArray( parent[env], art.name.id, art );
732
817
  }
733
818
  else if (kind || this.options.parseOnly) {
734
819
  dictAdd( parent[env], art.name.id, art );
735
820
  }
736
821
  else {
737
- dictAdd( parent[env], art.name.id, art, ( name, loc ) => {
822
+ dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
738
823
  // do not use function(), otherwise `this` is wrong:
739
824
  if (kind === 0) {
740
- this.error( 'duplicate-argument', loc, { name },
825
+ this.error( 'duplicate-argument', loc, { name: duplicateName },
741
826
  'Duplicate value for parameter $(NAME)' );
742
827
  }
743
828
  else if (kind === '') {
744
- this.error( 'duplicate-excluding', loc, { name, keyword: 'excluding' },
829
+ this.error( 'duplicate-excluding', loc, { name: duplicateName, keyword: 'excluding' },
745
830
  'Duplicate $(NAME) in the $(KEYWORD) clause' );
746
831
  }
747
832
  else {
748
- this.error( 'duplicate-prop', loc, { name },
833
+ this.error( 'duplicate-prop', loc, { name: duplicateName },
749
834
  'Duplicate value for structure property $(NAME)' );
750
835
  }
751
836
  } );
@@ -753,85 +838,78 @@ function addDef( parent, env, kind, name, annos, props, location ) {
753
838
  return art;
754
839
  }
755
840
 
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 );
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 );
777
846
  return art;
778
847
  }
779
-
780
848
  /**
781
- * For `annotate/extend E:elem.sub`, create the `elements` structure
782
- * 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
783
854
  *
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 );
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 );
798
877
  }
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
878
  }
804
879
 
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;
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
+ };
835
913
  }
836
914
 
837
915
  // Create AST node for prefix operator `op` and arguments `args`
@@ -879,14 +957,12 @@ function setOnce( target, prop, value, ...tokens ) {
879
957
  target[prop] = value;
880
958
  }
881
959
 
882
- function setMaxCardinality( art, token, max, inferred ) {
960
+ function setMaxCardinality( art, token, max ) {
883
961
  const location = this.tokenLocation( token );
884
962
  if (!art.cardinality) {
885
963
  art.cardinality = { targetMax: Object.assign( { location }, max ), location };
886
- if (inferred)
887
- art.cardinality.$inferred = inferred;
888
964
  }
889
- else if (!inferred) {
965
+ else {
890
966
  this.warning( 'syntax-repeated-cardinality', location, { keyword: token.text },
891
967
  'The target cardinality has already been specified - ignored $(KEYWORD)' );
892
968
  }
@@ -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;