@@ -150,7 +150,7 @@ Lexer.prototype = {
150
150
this . readString ( ch ) ;
151
151
} else if ( this . isNumber ( ch ) || ch === '.' && this . isNumber ( this . peek ( ) ) ) {
152
152
this . readNumber ( ) ;
153
- } else if ( this . isIdent ( ch ) ) {
153
+ } else if ( this . isIdentifierStart ( this . peekMultichar ( ) ) ) {
154
154
this . readIdent ( ) ;
155
155
} else if ( this . is ( ch , '(){}[].,;:?' ) ) {
156
156
this . tokens . push ( { index : this . index , text : ch } ) ;
@@ -194,12 +194,49 @@ Lexer.prototype = {
194
194
ch === '\n' || ch === '\v' || ch === '\u00A0' ) ;
195
195
} ,
196
196
197
- isIdent : function ( ch ) {
197
+ isIdentifierStart : function ( ch ) {
198
+ return this . options . isIdentifierStart ?
199
+ this . options . isIdentifierStart ( ch , this . codePointAt ( ch ) ) :
200
+ this . isValidIdentifierStart ( ch ) ;
201
+ } ,
202
+
203
+ isValidIdentifierStart : function ( ch ) {
198
204
return ( 'a' <= ch && ch <= 'z' ||
199
205
'A' <= ch && ch <= 'Z' ||
200
206
'_' === ch || ch === '$' ) ;
201
207
} ,
202
208
209
+ isIdentifierContinue : function ( ch ) {
210
+ return this . options . isIdentifierContinue ?
211
+ this . options . isIdentifierContinue ( ch , this . codePointAt ( ch ) ) :
212
+ this . isValidIdentifierContinue ( ch ) ;
213
+ } ,
214
+
215
+ isValidIdentifierContinue : function ( ch , cp ) {
216
+ return this . isValidIdentifierStart ( ch , cp ) || this . isNumber ( ch ) ;
217
+ } ,
218
+
219
+ codePointAt : function ( ch ) {
220
+ if ( ch . length === 1 ) return ch . charCodeAt ( 0 ) ;
221
+ /*jshint bitwise: false*/
222
+ return ( ch . charCodeAt ( 0 ) << 10 ) + ch . charCodeAt ( 1 ) - 0x35FDC00 ;
223
+ /*jshint bitwise: true*/
224
+ } ,
225
+
226
+ peekMultichar : function ( ) {
227
+ var ch = this . text . charAt ( this . index ) ;
228
+ var peek = this . peek ( ) ;
229
+ if ( ! peek ) {
230
+ return ch ;
231
+ }
232
+ var cp1 = ch . charCodeAt ( 0 ) ;
233
+ var cp2 = peek . charCodeAt ( 0 ) ;
234
+ if ( cp1 >= 0xD800 && cp1 <= 0xDBFF && cp2 >= 0xDC00 && cp2 <= 0xDFFF ) {
235
+ return ch + peek ;
236
+ }
237
+ return ch ;
238
+ } ,
239
+
203
240
isExpOperator : function ( ch ) {
204
241
return ( ch === '-' || ch === '+' || this . isNumber ( ch ) ) ;
205
242
} ,
@@ -248,12 +285,13 @@ Lexer.prototype = {
248
285
249
286
readIdent : function ( ) {
250
287
var start = this . index ;
288
+ this . index += this . peekMultichar ( ) . length ;
251
289
while ( this . index < this . text . length ) {
252
- var ch = this . text . charAt ( this . index ) ;
253
- if ( ! ( this . isIdent ( ch ) || this . isNumber ( ch ) ) ) {
290
+ var ch = this . peekMultichar ( ) ;
291
+ if ( ! this . isIdentifierContinue ( ch ) ) {
254
292
break ;
255
293
}
256
- this . index ++ ;
294
+ this . index += ch . length ;
257
295
}
258
296
this . tokens . push ( {
259
297
index : start ,
@@ -1183,7 +1221,13 @@ ASTCompiler.prototype = {
1183
1221
} ,
1184
1222
1185
1223
nonComputedMember : function ( left , right ) {
1186
- return left + '.' + right ;
1224
+ var SAFE_IDENTIFIER = / [ $ _ a - z A - Z ] [ $ _ a - z A - Z 0 - 9 ] * / ;
1225
+ var UNSAFE_CHARACTERS = / [ ^ $ _ a - z A - Z 0 - 9 ] / g;
1226
+ if ( SAFE_IDENTIFIER . test ( right ) ) {
1227
+ return left + '.' + right ;
1228
+ } else {
1229
+ return left + '["' + right . replace ( UNSAFE_CHARACTERS , this . stringEscapeFn ) + '"]' ;
1230
+ }
1187
1231
} ,
1188
1232
1189
1233
computedMember : function ( left , right ) {
@@ -1748,6 +1792,7 @@ function $ParseProvider() {
1748
1792
'null' : null ,
1749
1793
'undefined' : undefined
1750
1794
} ;
1795
+ var identStart , identContinue ;
1751
1796
1752
1797
/**
1753
1798
* @ngdoc method
@@ -1764,17 +1809,50 @@ function $ParseProvider() {
1764
1809
literals [ literalName ] = literalValue ;
1765
1810
} ;
1766
1811
1812
+ /**
1813
+ * @ngdoc method
1814
+ * @name $parseProvider#setIdentifierFns
1815
+ * @description
1816
+ *
1817
+ * Allows defining the set of characters that are allowed in Angular expressions. The function
1818
+ * `identifierStart` will get called to know if a given character is a valid character to be the
1819
+ * first character for an identifier. The function `identifierContinue` will get called to know if
1820
+ * a given character is a valid character to be a follow-up identifier character. The functions
1821
+ * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
1822
+ * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
1823
+ * mind that the `string` parameter can be two characters long depending on the character
1824
+ * representation. It is expected for the function to return `true` or `false`, whether that
1825
+ * character is allowed or not.
1826
+ *
1827
+ * Since this function will be called extensivelly, keep the implementation of these functions fast,
1828
+ * as the performance of these functions have a direct impact on the expressions parsing speed.
1829
+ *
1830
+ * @param {function= } identifierStart The function that will decide whether the given character is
1831
+ * a valid identifier start character.
1832
+ * @param {function= } identifierContinue The function that will decide whether the given character is
1833
+ * a valid identifier continue character.
1834
+ */
1835
+ this . setIdentifierFns = function ( identifierStart , identifierContinue ) {
1836
+ identStart = identifierStart ;
1837
+ identContinue = identifierContinue ;
1838
+ return this ;
1839
+ } ;
1840
+
1767
1841
this . $get = [ '$filter' , function ( $filter ) {
1768
1842
var noUnsafeEval = csp ( ) . noUnsafeEval ;
1769
1843
var $parseOptions = {
1770
1844
csp : noUnsafeEval ,
1771
1845
expensiveChecks : false ,
1772
- literals : copy ( literals )
1846
+ literals : copy ( literals ) ,
1847
+ isIdentifierStart : isFunction ( identStart ) && identStart ,
1848
+ isIdentifierContinue : isFunction ( identContinue ) && identContinue
1773
1849
} ,
1774
1850
$parseOptionsExpensive = {
1775
1851
csp : noUnsafeEval ,
1776
1852
expensiveChecks : true ,
1777
- literals : copy ( literals )
1853
+ literals : copy ( literals ) ,
1854
+ isIdentifierStart : isFunction ( identStart ) && identStart ,
1855
+ isIdentifierContinue : isFunction ( identContinue ) && identContinue
1778
1856
} ;
1779
1857
var runningChecksEnabled = false ;
1780
1858
0 commit comments