@sap/cds-compiler 2.10.4 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const whitespaceRegEx = /[\t\u{000B}\u{000C} \u{00A0}\u{FEFF}\p{Zs}]/u;
|
|
4
|
+
const newLineRegEx = /\r\n?|\n|\u2028|\u2029/u;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Returns true if the given string only contains whitespace characters.
|
|
8
|
+
*
|
|
9
|
+
* @todo Combine with function from docCommentParser
|
|
10
|
+
* @param {string} str
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
function isWhiteSpaceOnly(str) {
|
|
14
|
+
return /^\s*$/.test(str);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check whether the given character is a white-space character as
|
|
19
|
+
* defined by §11.2 of the ECMAScript 2020 specification.
|
|
20
|
+
* See <https://262.ecma-international.org/11.0/#sec-white-space>.
|
|
21
|
+
*
|
|
22
|
+
* | Code Point | Name | Abbreviation |
|
|
23
|
+
* |:--------------------|:-----------------------------------------------|--------------|
|
|
24
|
+
* | U+0009 | CHARACTER TABULATION | `<TAB>` |
|
|
25
|
+
* | U+000B | LINE TABULATION | `<VT>` |
|
|
26
|
+
* | U+000C | FORM FEED (FF) | `<FF>` |
|
|
27
|
+
* | U+0020 | SPACE | `<SP>` |
|
|
28
|
+
* | U+00A0 | NO-BREAK SPACE | `<NBSP>` |
|
|
29
|
+
* | U+FEFF | ZERO WIDTH NO-BREAK SPACE | `<ZWNBSP>` |
|
|
30
|
+
* | Other category “Zs” | Any other Unicode “Space_Separator” code point | `<USP>` |
|
|
31
|
+
*
|
|
32
|
+
* @param char
|
|
33
|
+
* @returns {boolean}
|
|
34
|
+
*/
|
|
35
|
+
function isWhitespaceCharacter(char) {
|
|
36
|
+
return whitespaceRegEx.test(char);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Strips and counts the indentation from the given string.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* | hello
|
|
44
|
+
* | world
|
|
45
|
+
* | foo bar
|
|
46
|
+
* becomes
|
|
47
|
+
* | hello
|
|
48
|
+
* | world
|
|
49
|
+
* | foo bar
|
|
50
|
+
*
|
|
51
|
+
* @param {string} str String prior to newline-normalization and escape parsing.
|
|
52
|
+
* @returns {[string, number]} The indentation-stripped string and the number
|
|
53
|
+
* of whitespace characters removed.
|
|
54
|
+
*/
|
|
55
|
+
function stripIndentation(str) {
|
|
56
|
+
if (str === '') {
|
|
57
|
+
return ['', 0];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Note: We have to check all newline characters, as the string is not normalized, yet.
|
|
61
|
+
const lines = str.split(newLineRegEx);
|
|
62
|
+
const n = lines.length;
|
|
63
|
+
|
|
64
|
+
const hasTrailingLineBreak = newLineRegEx.test(str[str.length - 1]);
|
|
65
|
+
if (hasTrailingLineBreak) {
|
|
66
|
+
// Shortcut:
|
|
67
|
+
// If there is a trailing line break, it means that ``` is on newline and
|
|
68
|
+
// therefore the indentation to remove is 0.
|
|
69
|
+
// Remove the last newline, which may be CRLF.
|
|
70
|
+
return [lines.slice(0, -1).join('\n'), 0];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const min = lines.reduce((min, line, index) => {
|
|
74
|
+
// Note: Last line is the line containing ```. There, we always count the indentation,
|
|
75
|
+
// even if blank. For all other lines, blank lines are ignored.
|
|
76
|
+
if (isWhiteSpaceOnly(line) && index !== (n-1))
|
|
77
|
+
return min;
|
|
78
|
+
|
|
79
|
+
let count = 0;
|
|
80
|
+
const length = Math.min(min, line.length);
|
|
81
|
+
while (count < length && isWhitespaceCharacter(line[count])) {
|
|
82
|
+
count++;
|
|
83
|
+
}
|
|
84
|
+
return Math.min(min, count);
|
|
85
|
+
}, Number.MAX_SAFE_INTEGER);
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < n; ++i) {
|
|
88
|
+
// Note: Line may be empty and have fewer characters than `min`.
|
|
89
|
+
// In that case, slice() returns an empty string.
|
|
90
|
+
lines[i] = lines[i].slice(min);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Remove trailing last line, if there was nothing else in that line.
|
|
94
|
+
if (lines[n-1] === '')
|
|
95
|
+
lines.pop();
|
|
96
|
+
|
|
97
|
+
return [lines.join('\n'), min];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class MultiLineStringParser {
|
|
101
|
+
constructor(antlrParser, token) {
|
|
102
|
+
this.parser = antlrParser; // for message functions
|
|
103
|
+
this.token = token;
|
|
104
|
+
this.str = token.text; // Copy because .text is a getter
|
|
105
|
+
|
|
106
|
+
if (this.str[0] !== '`' || this.str[this.str.length-1] !== '`') {
|
|
107
|
+
throw new Error('Invalid multi-line string sequence: Require string to be surrounded by back-ticks!');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.output = [];
|
|
111
|
+
this.isTextBlock = this.str.startsWith('```');
|
|
112
|
+
this._indentation = 0;
|
|
113
|
+
|
|
114
|
+
// For message locations
|
|
115
|
+
this._lineInString = 0;
|
|
116
|
+
this._currentLineBreakIndex = 0;
|
|
117
|
+
|
|
118
|
+
if (this.isTextBlock) {
|
|
119
|
+
this.i = 3;
|
|
120
|
+
this.end = this.str.length - 3;
|
|
121
|
+
} else {
|
|
122
|
+
this.i = 1;
|
|
123
|
+
this.end = this.str.length - 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Parse the token's text and return it.
|
|
129
|
+
*
|
|
130
|
+
* @return {string}
|
|
131
|
+
*/
|
|
132
|
+
parse() {
|
|
133
|
+
if (this.str.length === 2) {
|
|
134
|
+
return ''; // Nothing to do: ``
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (this.isTextBlock) {
|
|
138
|
+
// If there are no line breaks, emit an error as normal single-back-tick
|
|
139
|
+
// strings should be used instead. Because the first line is skipped,
|
|
140
|
+
// there is no text without at least one line break.
|
|
141
|
+
if (!newLineRegEx.test(this.str)) {
|
|
142
|
+
const loc = this._locationForCharacters(this.end, 1);
|
|
143
|
+
this.parser.message('syntax-invalid-text-block', loc);
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
this._skipOptionalLanguageIdentifierLine();
|
|
147
|
+
// Indentation needs to be stripped _before_ escape sequences are parsed and
|
|
148
|
+
// _after_ the first line is skipped, because otherwise `\n` in the string
|
|
149
|
+
// will interfere with calculating indentation and the language identifier
|
|
150
|
+
// is not part of the actual string.
|
|
151
|
+
// Because of message locations, we still need to keep track of indentation count
|
|
152
|
+
// and need to update the cursor and end position as well as the currentLineBreakIndex.
|
|
153
|
+
const [ str, indent ] = stripIndentation(this.str.slice(this.i, -3));
|
|
154
|
+
this.str = str;
|
|
155
|
+
this._indentation = indent;
|
|
156
|
+
this.i = 0;
|
|
157
|
+
this.end = this.str.length;
|
|
158
|
+
// this._lineInString is > 0, but having this._currentLineBreakIndex = 0 would be incorrect,
|
|
159
|
+
// as the line break isn't the first character in the indentation-stripped string
|
|
160
|
+
this._currentLineBreakIndex = -1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Note: Index is at first character of string
|
|
164
|
+
|
|
165
|
+
do {
|
|
166
|
+
switch(this._current()) {
|
|
167
|
+
case this._matchLineBreakAtCurrentChar():
|
|
168
|
+
this.output.push('\n');
|
|
169
|
+
break;
|
|
170
|
+
case '\\':
|
|
171
|
+
this._move();
|
|
172
|
+
this._innerEscape();
|
|
173
|
+
break;
|
|
174
|
+
case '$':
|
|
175
|
+
if (this._lookahead() === '{') {
|
|
176
|
+
const loc = this._locationForCharacters(this.i, 2);
|
|
177
|
+
this.parser.message('syntax-missing-escape', loc,
|
|
178
|
+
{ '#': 'placeholder', code: '${', newcode: '\\${' });
|
|
179
|
+
}
|
|
180
|
+
this.output.push(this.str[this.i]);
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
this.output.push(this.str[this.i]);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
} while(this._move());
|
|
187
|
+
|
|
188
|
+
return this.output.join('');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse the escape sequence after the first '\'.
|
|
193
|
+
*
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_innerEscape() {
|
|
197
|
+
switch(this._current()) {
|
|
198
|
+
case this._matchLineBreakAtCurrentChar():
|
|
199
|
+
// Don't add to output -> line break is escaped
|
|
200
|
+
break;
|
|
201
|
+
case 'b': // backspace
|
|
202
|
+
this.output.push(`\b`);
|
|
203
|
+
break;
|
|
204
|
+
case 'f': // form feed
|
|
205
|
+
this.output.push(`\f`);
|
|
206
|
+
break;
|
|
207
|
+
case 'v': // vertical tabulator
|
|
208
|
+
this.output.push(`\v`);
|
|
209
|
+
break;
|
|
210
|
+
case 'r': // carriage return
|
|
211
|
+
this.output.push(`\r`);
|
|
212
|
+
break;
|
|
213
|
+
case 'n': // line feed
|
|
214
|
+
this.output.push(`\n`);
|
|
215
|
+
break;
|
|
216
|
+
case 't': // tab
|
|
217
|
+
this.output.push(`\t`);
|
|
218
|
+
break;
|
|
219
|
+
case '\\':
|
|
220
|
+
case '"':
|
|
221
|
+
case '\'':
|
|
222
|
+
case '`':
|
|
223
|
+
case '$':
|
|
224
|
+
this.output.push(this._current());
|
|
225
|
+
break;
|
|
226
|
+
case 'x':
|
|
227
|
+
this._parseHexEscape('x', 2);
|
|
228
|
+
break;
|
|
229
|
+
case 'u':
|
|
230
|
+
if (this._lookahead() === '{')
|
|
231
|
+
this._parseBracedUnicodeEscape();
|
|
232
|
+
else
|
|
233
|
+
this._parseHexEscape('u', 4);
|
|
234
|
+
break;
|
|
235
|
+
case '0': // null terminator
|
|
236
|
+
if (!/^\d$/.test(this._lookahead())) {
|
|
237
|
+
this.output.push(`\0`);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
// Let the default case handle octal representation.
|
|
241
|
+
// fallthrough
|
|
242
|
+
default: {
|
|
243
|
+
this.output.push(this._current());
|
|
244
|
+
const loc = this._locationForCharacters(this.i-1, 2);
|
|
245
|
+
if (/\s/.test(this._current())) {
|
|
246
|
+
this.parser.message('syntax-invalid-escape', loc, { '#': 'whitespace' });
|
|
247
|
+
} else if (/\d/.test(this._current())) {
|
|
248
|
+
this.parser.message('syntax-invalid-escape', loc, { '#': 'octal' });
|
|
249
|
+
} else {
|
|
250
|
+
const code = this._makeCode('\\' + this._current());
|
|
251
|
+
this.parser.message('syntax-unknown-escape', loc, { '#': 'std', code });
|
|
252
|
+
}
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Parse the given hexadecimal string to a unicode code-point.
|
|
260
|
+
*
|
|
261
|
+
* @param {string} codePoint Code-point represented as hexadecimal string, e.g. 'ABCD'.
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
_parseHexCodePoint(codePoint) {
|
|
265
|
+
// Notes:
|
|
266
|
+
// It isn't possible to get an invalid code point with the \u0000
|
|
267
|
+
// syntax variant as the first invalid code point is \u{110000}
|
|
268
|
+
// and an empty `codePoint` is only possible with the braced variant.
|
|
269
|
+
const reportInvalidCodePoint = () => {
|
|
270
|
+
const code = this._makeCode(`\\u{${codePoint}}`);
|
|
271
|
+
const loc = this._locationForCharacters(this.i-codePoint.length, codePoint.length);
|
|
272
|
+
this.parser.message('syntax-invalid-escape', loc, { '#': 'codepoint', code });
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const n = Number.parseInt(codePoint, 16);
|
|
276
|
+
if (Number.isNaN(n)) {
|
|
277
|
+
reportInvalidCodePoint();
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
this.output.push(String.fromCodePoint(n));
|
|
283
|
+
} catch (e) {
|
|
284
|
+
// RangeError is thrown if number isn't a valid code point
|
|
285
|
+
reportInvalidCodePoint();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Parse a hex escape-sequence. Useful for unicode escapes and hex escapes.
|
|
291
|
+
* Cursor is at the `x`: `\x00`
|
|
292
|
+
* ^
|
|
293
|
+
* or at the `u`: `\u0000`
|
|
294
|
+
* ^
|
|
295
|
+
* @param {string} mode Either `x` or `u`. Used for error messages.
|
|
296
|
+
* @param {number} count Number of expected hexadecimal numbers
|
|
297
|
+
* @private
|
|
298
|
+
*/
|
|
299
|
+
_parseHexEscape(mode, count) {
|
|
300
|
+
let codePoint = '';
|
|
301
|
+
|
|
302
|
+
for (let j = 0; j < count; ++j) {
|
|
303
|
+
if (!this._eos() && /^[0-9A-Fa-f]$/.test(this._lookahead())) {
|
|
304
|
+
this._move();
|
|
305
|
+
codePoint += this._current();
|
|
306
|
+
} else {
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (codePoint.length === count) {
|
|
312
|
+
this._parseHexCodePoint(codePoint);
|
|
313
|
+
} else {
|
|
314
|
+
const loc = this._locationForCharacters(this.i+1, 1);
|
|
315
|
+
const code = this._eos(this.i+1) ? `\\${mode}${codePoint}` : `\\${mode}${codePoint}${this._lookahead()}`;
|
|
316
|
+
this.parser.message('syntax-invalid-escape', loc,
|
|
317
|
+
{ '#': 'hex-count', number: count, code: this._makeCode(code) });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Parse a unicode escape-sequence with braces.
|
|
323
|
+
* Cursor is at the `u`: `\u{0000}`
|
|
324
|
+
* ^
|
|
325
|
+
* @private
|
|
326
|
+
*/
|
|
327
|
+
_parseBracedUnicodeEscape() {
|
|
328
|
+
let codePoint = '';
|
|
329
|
+
|
|
330
|
+
this._move(); // 'u'
|
|
331
|
+
|
|
332
|
+
while (!this._eos()) {
|
|
333
|
+
if (/^[0-9A-Fa-f]$/.test(this._lookahead())) {
|
|
334
|
+
this._move();
|
|
335
|
+
codePoint += this._current();
|
|
336
|
+
} else if (this._lookahead() === '}') {
|
|
337
|
+
break;
|
|
338
|
+
} else if (!this._eos(this.i+1)) {
|
|
339
|
+
const loc = this._locationForCharacters(this.i+1, 1); // Point to the exact character
|
|
340
|
+
const code = this._makeCode(`\\u{${codePoint}${this._lookahead()}…}`);
|
|
341
|
+
this.parser.message('syntax-invalid-escape', loc, { '#': 'unicode-hex', code });
|
|
342
|
+
return;
|
|
343
|
+
} else {
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (this._lookahead() === '}') {
|
|
349
|
+
this._move();
|
|
350
|
+
this._parseHexCodePoint(codePoint);
|
|
351
|
+
} else {
|
|
352
|
+
const loc = this._locationForCharacters(this.i, 1);
|
|
353
|
+
this.parser.message('syntax-invalid-escape', loc, { '#': 'unicode-brace' });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* This function skips the language identifier, i.e. until the next line.
|
|
359
|
+
* After this function, the cursor will be at the character _after_ the newline.
|
|
360
|
+
*
|
|
361
|
+
* @private
|
|
362
|
+
*/
|
|
363
|
+
_skipOptionalLanguageIdentifierLine() {
|
|
364
|
+
while(!this._eos()) {
|
|
365
|
+
switch (this._current()) {
|
|
366
|
+
case this._matchLineBreakAtCurrentChar():
|
|
367
|
+
this._move();
|
|
368
|
+
return;
|
|
369
|
+
case '\\': {
|
|
370
|
+
// Do not allow an escape in the language identifier. If at the line's end, users
|
|
371
|
+
// may expect the identifier to span more than the first line, which is _not_ the case.
|
|
372
|
+
const loc = this._locationForCharacters(this.i, 1);
|
|
373
|
+
this.parser.message('syntax-invalid-escape', loc, { '#': 'language-identifier' });
|
|
374
|
+
this._move();
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
default:
|
|
378
|
+
this._move();
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Consume a line-break Character. Because CDS is close to JavaScript, we
|
|
386
|
+
* also support LS and PS. This function also ensures that CRLF (`\r\n`) is
|
|
387
|
+
* recognized as a single character.
|
|
388
|
+
* We increase the line number for LF (`\n`) for correct message locations.
|
|
389
|
+
*
|
|
390
|
+
* This function returns the input character, so that it can be used
|
|
391
|
+
* in a switch-case.
|
|
392
|
+
*
|
|
393
|
+
* @returns {string|null}
|
|
394
|
+
* @private
|
|
395
|
+
*/
|
|
396
|
+
_matchLineBreakAtCurrentChar() {
|
|
397
|
+
// Only increase line number for \n, because ANTLR does the same
|
|
398
|
+
switch(this._current()) {
|
|
399
|
+
case '\r':
|
|
400
|
+
if (this._lookahead() === '\n') {
|
|
401
|
+
this._move(); // \r\n is normalized
|
|
402
|
+
this._lineInString++;
|
|
403
|
+
this._currentLineBreakIndex = this.i;
|
|
404
|
+
}
|
|
405
|
+
return '\r';
|
|
406
|
+
case '\n':
|
|
407
|
+
this._lineInString++;
|
|
408
|
+
this._currentLineBreakIndex = this.i;
|
|
409
|
+
// fallthrough
|
|
410
|
+
case '\u2028': // LS
|
|
411
|
+
case '\u2029': // PS
|
|
412
|
+
return this._current();
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Move the cursor to the next character _if_ we're not at the end.
|
|
419
|
+
*
|
|
420
|
+
* @private
|
|
421
|
+
* @returns {boolean} `true` if we're not at the end
|
|
422
|
+
*/
|
|
423
|
+
_move() {
|
|
424
|
+
if (this.i < this.end) { // Don't move past last char and `
|
|
425
|
+
++this.i;
|
|
426
|
+
}
|
|
427
|
+
return this.i < this.end;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Returns `true` if we're at the end of the string
|
|
432
|
+
*
|
|
433
|
+
* @param {Number} [i=this.i] Index to check for EOS
|
|
434
|
+
* @private
|
|
435
|
+
* @returns {boolean}
|
|
436
|
+
*/
|
|
437
|
+
_eos(i = this.i) {
|
|
438
|
+
// end-of-string -> char before `
|
|
439
|
+
return i >= this.end;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Get the next character without increasing the cursor.
|
|
444
|
+
* @note Does not check for `eos()`
|
|
445
|
+
*
|
|
446
|
+
* @private
|
|
447
|
+
* @returns {string}
|
|
448
|
+
*/
|
|
449
|
+
_lookahead() {
|
|
450
|
+
return this.str[this.i+1];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Get the current character without increasing the cursor.
|
|
455
|
+
*
|
|
456
|
+
* @private
|
|
457
|
+
* @returns {string}
|
|
458
|
+
*/
|
|
459
|
+
_current() {
|
|
460
|
+
return this.str[this.i];
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get the previous character without decreasing the cursor.
|
|
465
|
+
*
|
|
466
|
+
* @private
|
|
467
|
+
* @returns {string}
|
|
468
|
+
*/
|
|
469
|
+
_previous() {
|
|
470
|
+
return this.str[this.i-1];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Get message location for the given cursor position inside the string.
|
|
475
|
+
*
|
|
476
|
+
* @param {Number} i Cursor position
|
|
477
|
+
* @param {Number} width Width of the location
|
|
478
|
+
* @private
|
|
479
|
+
* @returns {CSN.Location}
|
|
480
|
+
*/
|
|
481
|
+
_locationForCharacters(i, width) {
|
|
482
|
+
return {
|
|
483
|
+
file: this.parser.filename,
|
|
484
|
+
line: this.token.line + this._lineInString,
|
|
485
|
+
endLine: this.token.line + this._lineInString,
|
|
486
|
+
col: this._lineInString > 0 ? i - this._currentLineBreakIndex + this._indentation : this.token.column + i + 1,
|
|
487
|
+
endCol: this._lineInString > 0 ? i - this._currentLineBreakIndex + width + this._indentation : this.token.column + i + width + 1,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* For text messages, escape the given string for $(CODE).
|
|
493
|
+
* Escaping is required to avoid line breaks in compiler messages, e.g.
|
|
494
|
+
* if \u000<LF> is the code, the line-feed must be escaped.
|
|
495
|
+
*
|
|
496
|
+
* @param {string} code
|
|
497
|
+
* @private
|
|
498
|
+
*/
|
|
499
|
+
_makeCode(code) {
|
|
500
|
+
// For characters that may be rendered as newline,
|
|
501
|
+
// see <https://www.unicode.org/reports/tr14/tr14-32.html>.
|
|
502
|
+
//
|
|
503
|
+
// Note: Unicode class `General_Category=Line_Separator` does not work for '\n'.
|
|
504
|
+
//
|
|
505
|
+
// U+000A: Line Feed (short: LF)
|
|
506
|
+
// U+000B: Vertical Tab (short: VT)
|
|
507
|
+
// U+000C: Form Feed (short: FF)
|
|
508
|
+
// U+000D: Carriage Return (short: CR)
|
|
509
|
+
// U+0085: Next Line (short: NEL)
|
|
510
|
+
// U+2028: Line Separator (short: LS)
|
|
511
|
+
// U+2029: Paragraph Separator (short: PS)
|
|
512
|
+
//
|
|
513
|
+
// For Visualization, see <https://en.wikipedia.org/wiki/Newline#Unicode>
|
|
514
|
+
// U+23CE: ⏎
|
|
515
|
+
|
|
516
|
+
const newLineRegEx = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
|
|
517
|
+
return code.replace(newLineRegEx, '\u{23CE}');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Parse a back-tick string and return it. This includes escape
|
|
523
|
+
* sequences, newlines, etc.
|
|
524
|
+
*
|
|
525
|
+
* Does _not_ modify the token's text.
|
|
526
|
+
*
|
|
527
|
+
* @param {object} token
|
|
528
|
+
*/
|
|
529
|
+
function parseMultiLineStringLiteral(token) {
|
|
530
|
+
const p = new MultiLineStringParser(this, token);
|
|
531
|
+
return p.parse();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
module.exports = {
|
|
535
|
+
parseMultiLineStringLiteral,
|
|
536
|
+
};
|