@angular-wave/angular.ts 0.0.48 → 0.0.50
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/Makefile +3 -0
- package/README.md +1 -1
- package/css/angular.css +0 -6
- package/dist/angular-ts.esm.js +2 -2
- package/dist/angular-ts.umd.js +2 -2
- package/jsdoc.json +24 -0
- package/package.json +6 -2
- package/src/core/compile/compile.md +1 -1
- package/src/core/compile/compile.spec.js +50 -48
- package/src/core/interpolate/interpolate.js +1 -18
- package/src/core/on.spec.js +7 -12
- package/src/core/parser/ast.js +234 -69
- package/src/core/parser/interpreter.js +246 -50
- package/src/core/parser/lexer.js +144 -57
- package/src/core/parser/parse.js +38 -278
- package/src/core/parser/parse.md +44 -0
- package/src/core/parser/parser.js +32 -6
- package/src/core/parser/shared.js +7 -1
- package/src/core/prop.spec.js +4 -4
- package/src/core/scope/scope.js +14 -10
- package/src/directive/csp.md +0 -26
- package/src/directive/form/form.spec.js +18 -18
- package/src/directive/include/include.spec.js +18 -18
- package/src/directive/switch/switch.spec.js +4 -4
- package/src/shared/constants.js +3 -2
- package/src/shared/jqlite/jqlite.js +2 -2
- package/src/types.js +4 -1
- package/types/core/parser/ast.d.ts +269 -67
- package/types/core/parser/interpreter.d.ts +202 -53
- package/types/core/parser/lexer.d.ts +153 -0
- package/types/core/parser/parse.d.ts +23 -34
- package/types/core/parser/parser.d.ts +43 -14
- package/types/core/parser/shared.d.ts +7 -1
- package/types/core/scope/scope.d.ts +11 -11
- package/types/shared/jqlite/jqlite.d.ts +4 -4
- package/types/types.d.ts +1 -1
- package/src/core/parser/compiler.js +0 -561
- package/types/core/parser/compiler.d.ts +0 -49
package/src/core/parser/lexer.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { $parseMinErr } from "./parse";
|
|
2
|
-
|
|
3
2
|
import { isDefined } from "../../shared/utils";
|
|
4
3
|
|
|
5
4
|
const ESCAPE = {
|
|
@@ -17,18 +16,45 @@ const OPERATORS = new Set(
|
|
|
17
16
|
);
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
|
-
* @
|
|
19
|
+
* @typedef {Object} LexerOptions
|
|
20
|
+
* @property {(ch: string, codePoint: number) => boolean} [isIdentifierStart] - Custom function to determine if a character is a valid identifier start.
|
|
21
|
+
* @property {(ch: string, codePoint: number) => boolean} [isIdentifierContinue] - Custom function to determine if a character is a valid identifier continuation.
|
|
21
22
|
*/
|
|
22
|
-
export const Lexer = function Lexer(options) {
|
|
23
|
-
this.options = options;
|
|
24
|
-
};
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Represents a token produced by the lexer.
|
|
26
|
+
* @typedef {Object} Token
|
|
27
|
+
* @property {number} index - Index of the token.
|
|
28
|
+
* @property {string} text - Text of the token.
|
|
29
|
+
* @property {boolean} [identifier] - Indicates if token is an identifier.
|
|
30
|
+
* @property {boolean} [constant] - Indicates if token is a constant.
|
|
31
|
+
* @property {string|number} [value] - Value of the token if it's a constant.
|
|
32
|
+
* @property {boolean} [operator] - Indicates if token is an operator.
|
|
33
|
+
*/
|
|
28
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Represents a lexer that tokenizes input text. The Lexer takes the original expression string and returns an array of tokens parsed from that string.
|
|
37
|
+
* For example, the string "a + b" would result in tokens for a, +, and b.
|
|
38
|
+
*/
|
|
39
|
+
export class Lexer {
|
|
40
|
+
/**
|
|
41
|
+
* Creates an instance of Lexer.
|
|
42
|
+
* @param {LexerOptions} options - Lexer options.
|
|
43
|
+
*/
|
|
44
|
+
constructor(options) {
|
|
45
|
+
/** @type {LexerOptions} */
|
|
46
|
+
this.options = options;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Tokenizes the input text.
|
|
51
|
+
* @param {string} text Input text to lex.
|
|
52
|
+
* @returns {Array<Token>} Array of tokens.
|
|
53
|
+
*/
|
|
29
54
|
lex(text) {
|
|
30
55
|
this.text = text;
|
|
31
56
|
this.index = 0;
|
|
57
|
+
/** @type {Array<Token>} */
|
|
32
58
|
this.tokens = [];
|
|
33
59
|
|
|
34
60
|
while (this.index < this.text.length) {
|
|
@@ -37,7 +63,7 @@ Lexer.prototype = {
|
|
|
37
63
|
this.readString(ch);
|
|
38
64
|
} else if (
|
|
39
65
|
this.isNumber(ch) ||
|
|
40
|
-
(ch === "." && this.isNumber(this.peek()))
|
|
66
|
+
(ch === "." && this.isNumber(/** @type {string} */ (this.peek())))
|
|
41
67
|
) {
|
|
42
68
|
this.readNumber();
|
|
43
69
|
} else if (this.isIdentifierStart(this.peekMultichar())) {
|
|
@@ -67,23 +93,44 @@ Lexer.prototype = {
|
|
|
67
93
|
}
|
|
68
94
|
}
|
|
69
95
|
return this.tokens;
|
|
70
|
-
}
|
|
96
|
+
}
|
|
71
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Checks if a character is contained in a set of characters.
|
|
100
|
+
* @param {string} ch Character to check.
|
|
101
|
+
* @param {string} chars Set of characters.
|
|
102
|
+
* @returns {boolean} True if character is in the set, false otherwise.
|
|
103
|
+
*/
|
|
72
104
|
is(ch, chars) {
|
|
73
105
|
return chars.indexOf(ch) !== -1;
|
|
74
|
-
}
|
|
106
|
+
}
|
|
75
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Peeks at the next character in the text.
|
|
110
|
+
* @param {number} [i=1] Number of characters to peek.
|
|
111
|
+
* @returns {string|false} Next character or false if end of text.
|
|
112
|
+
*/
|
|
76
113
|
peek(i) {
|
|
77
114
|
const num = i || 1;
|
|
78
115
|
return this.index + num < this.text.length
|
|
79
116
|
? this.text.charAt(this.index + num)
|
|
80
117
|
: false;
|
|
81
|
-
}
|
|
118
|
+
}
|
|
82
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Checks if a character is a number.
|
|
122
|
+
* @param {string} ch Character to check.
|
|
123
|
+
* @returns {boolean} True if character is a number, false otherwise.
|
|
124
|
+
*/
|
|
83
125
|
isNumber(ch) {
|
|
84
126
|
return ch >= "0" && ch <= "9" && typeof ch === "string";
|
|
85
|
-
}
|
|
127
|
+
}
|
|
86
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Checks if a character is whitespace.
|
|
131
|
+
* @param {string} ch Character to check.
|
|
132
|
+
* @returns {boolean} True if character is whitespace, false otherwise.
|
|
133
|
+
*/
|
|
87
134
|
isWhitespace(ch) {
|
|
88
135
|
// IE treats non-breaking space as \u00A0
|
|
89
136
|
return (
|
|
@@ -94,39 +141,52 @@ Lexer.prototype = {
|
|
|
94
141
|
ch === "\v" ||
|
|
95
142
|
ch === "\u00A0"
|
|
96
143
|
);
|
|
97
|
-
}
|
|
144
|
+
}
|
|
98
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Checks if a character is a valid identifier start.
|
|
148
|
+
* @param {string} ch Character to check.
|
|
149
|
+
* @returns {boolean} True if character is a valid identifier start, false otherwise.
|
|
150
|
+
*/
|
|
99
151
|
isIdentifierStart(ch) {
|
|
100
152
|
return this.options.isIdentifierStart
|
|
101
153
|
? this.options.isIdentifierStart(ch, this.codePointAt(ch))
|
|
102
|
-
:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
(ch >= "a" && ch <= "z") ||
|
|
108
|
-
(ch >= "A" && ch <= "Z") ||
|
|
109
|
-
ch === "_" ||
|
|
110
|
-
ch === "$"
|
|
111
|
-
);
|
|
112
|
-
},
|
|
154
|
+
: (ch >= "a" && ch <= "z") ||
|
|
155
|
+
(ch >= "A" && ch <= "Z") ||
|
|
156
|
+
ch === "_" ||
|
|
157
|
+
ch === "$";
|
|
158
|
+
}
|
|
113
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Checks if a character is a valid identifier continuation.
|
|
162
|
+
* @param {string} ch Character to check.
|
|
163
|
+
* @returns {boolean} True if character is a valid identifier continuation, false otherwise.
|
|
164
|
+
*/
|
|
114
165
|
isIdentifierContinue(ch) {
|
|
115
166
|
return this.options.isIdentifierContinue
|
|
116
167
|
? this.options.isIdentifierContinue(ch, this.codePointAt(ch))
|
|
117
|
-
:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
168
|
+
: (ch >= "a" && ch <= "z") ||
|
|
169
|
+
(ch >= "A" && ch <= "Z") ||
|
|
170
|
+
ch === "_" ||
|
|
171
|
+
ch === "$" ||
|
|
172
|
+
(ch >= "0" && ch <= "9");
|
|
173
|
+
}
|
|
123
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Converts a character to its Unicode code point.
|
|
177
|
+
* @param {string} ch Character to convert.
|
|
178
|
+
* @returns {number} Unicode code point.
|
|
179
|
+
*/
|
|
124
180
|
codePointAt(ch) {
|
|
125
181
|
if (ch.length === 1) return ch.charCodeAt(0);
|
|
126
182
|
|
|
127
183
|
return (ch.charCodeAt(0) << 10) + ch.charCodeAt(1) - 0x35fdc00;
|
|
128
|
-
}
|
|
184
|
+
}
|
|
129
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Peeks at the next multicharacter sequence in the text.
|
|
188
|
+
* @returns {string} Next multicharacter sequence.
|
|
189
|
+
*/
|
|
130
190
|
peekMultichar() {
|
|
131
191
|
const ch = this.text.charAt(this.index);
|
|
132
192
|
const peek = this.peek();
|
|
@@ -139,12 +199,24 @@ Lexer.prototype = {
|
|
|
139
199
|
return ch + peek;
|
|
140
200
|
}
|
|
141
201
|
return ch;
|
|
142
|
-
}
|
|
202
|
+
}
|
|
143
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Checks if a character is an exponent operator.
|
|
206
|
+
* @param {string} ch Character to check.
|
|
207
|
+
* @returns {boolean} True if character is an exponent operator, false otherwise.
|
|
208
|
+
*/
|
|
144
209
|
isExpOperator(ch) {
|
|
145
210
|
return ch === "-" || ch === "+" || this.isNumber(ch);
|
|
146
|
-
}
|
|
211
|
+
}
|
|
147
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Throws a lexer error.
|
|
215
|
+
* @param {string} error Error message.
|
|
216
|
+
* @param {number} [start] Start index.
|
|
217
|
+
* @param {number} [end] End index.
|
|
218
|
+
* @throws {Error} Lexer error.
|
|
219
|
+
*/
|
|
148
220
|
throwError(error, start, end) {
|
|
149
221
|
end = end || this.index;
|
|
150
222
|
const colStr = isDefined(start)
|
|
@@ -152,13 +224,13 @@ Lexer.prototype = {
|
|
|
152
224
|
: ` ${end}`;
|
|
153
225
|
throw $parseMinErr(
|
|
154
226
|
"lexerr",
|
|
155
|
-
|
|
156
|
-
error,
|
|
157
|
-
colStr,
|
|
158
|
-
this.text,
|
|
227
|
+
`Lexer Error: ${error} at column${colStr} in expression [${this.text}].`,
|
|
159
228
|
);
|
|
160
|
-
}
|
|
229
|
+
}
|
|
161
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Reads and tokenizes a number from the text.
|
|
233
|
+
*/
|
|
162
234
|
readNumber() {
|
|
163
235
|
let number = "";
|
|
164
236
|
const start = this.index;
|
|
@@ -168,7 +240,7 @@ Lexer.prototype = {
|
|
|
168
240
|
number += ch;
|
|
169
241
|
} else {
|
|
170
242
|
const peekCh = this.peek();
|
|
171
|
-
if (ch === "e" && this.isExpOperator(peekCh)) {
|
|
243
|
+
if (ch === "e" && this.isExpOperator(/** @type {string} */ (peekCh))) {
|
|
172
244
|
number += ch;
|
|
173
245
|
} else if (
|
|
174
246
|
this.isExpOperator(ch) &&
|
|
@@ -195,8 +267,11 @@ Lexer.prototype = {
|
|
|
195
267
|
constant: true,
|
|
196
268
|
value: Number(number),
|
|
197
269
|
});
|
|
198
|
-
}
|
|
270
|
+
}
|
|
199
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Reads and tokenizes an identifier from the text.
|
|
274
|
+
*/
|
|
200
275
|
readIdent() {
|
|
201
276
|
const start = this.index;
|
|
202
277
|
this.index += this.peekMultichar().length;
|
|
@@ -212,46 +287,58 @@ Lexer.prototype = {
|
|
|
212
287
|
text: this.text.slice(start, this.index),
|
|
213
288
|
identifier: true,
|
|
214
289
|
});
|
|
215
|
-
}
|
|
290
|
+
}
|
|
216
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Reads and tokenizes a string from the text.
|
|
294
|
+
* @param {string} quote Quote character used for the string.
|
|
295
|
+
*/
|
|
217
296
|
readString(quote) {
|
|
218
297
|
const start = this.index;
|
|
219
|
-
this.index++;
|
|
220
298
|
let string = "";
|
|
221
|
-
let rawString = quote;
|
|
222
299
|
let escape = false;
|
|
300
|
+
|
|
301
|
+
this.index++; // Skip opening quote
|
|
302
|
+
|
|
223
303
|
while (this.index < this.text.length) {
|
|
224
|
-
const ch = this.text
|
|
225
|
-
|
|
304
|
+
const ch = this.text[this.index];
|
|
305
|
+
|
|
226
306
|
if (escape) {
|
|
227
307
|
if (ch === "u") {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
this.index += 4;
|
|
233
|
-
string += String.fromCharCode(parseInt(hex, 16));
|
|
308
|
+
// Handle unicode escapes
|
|
309
|
+
// Simplified for brevity
|
|
310
|
+
string += this.handleUnicodeEscape();
|
|
234
311
|
} else {
|
|
235
|
-
|
|
236
|
-
string += rep || ch;
|
|
312
|
+
string += ESCAPE[ch] || ch;
|
|
237
313
|
}
|
|
238
314
|
escape = false;
|
|
239
315
|
} else if (ch === "\\") {
|
|
240
316
|
escape = true;
|
|
241
317
|
} else if (ch === quote) {
|
|
242
|
-
this.index++;
|
|
243
318
|
this.tokens.push({
|
|
244
319
|
index: start,
|
|
245
|
-
text:
|
|
320
|
+
text: this.text.slice(start, this.index + 1),
|
|
246
321
|
constant: true,
|
|
247
322
|
value: string,
|
|
248
323
|
});
|
|
324
|
+
this.index++; // Skip closing quote
|
|
249
325
|
return;
|
|
250
326
|
} else {
|
|
251
327
|
string += ch;
|
|
252
328
|
}
|
|
329
|
+
|
|
253
330
|
this.index++;
|
|
254
331
|
}
|
|
332
|
+
|
|
255
333
|
this.throwError("Unterminated quote", start);
|
|
256
|
-
}
|
|
257
|
-
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
handleUnicodeEscape() {
|
|
337
|
+
const hex = this.text.substring(this.index + 1, this.index + 5);
|
|
338
|
+
if (!hex.match(/[\da-f]{4}/i)) {
|
|
339
|
+
this.throwError(`Invalid unicode escape [\\u${hex}]`);
|
|
340
|
+
}
|
|
341
|
+
this.index += 4; // Move index past the four hexadecimal digits
|
|
342
|
+
return String.fromCharCode(parseInt(hex, 16));
|
|
343
|
+
}
|
|
344
|
+
}
|