@@ -150,7 +150,7 @@ Lexer.prototype = {
150150 this . readString ( ch ) ;
151151 } else if ( this . isNumber ( ch ) || ch === '.' && this . isNumber ( this . peek ( ) ) ) {
152152 this . readNumber ( ) ;
153- } else if ( this . isIdent ( ch ) ) {
153+ } else if ( this . isIdentifierStart ( this . peekMultichar ( ) ) ) {
154154 this . readIdent ( ) ;
155155 } else if ( this . is ( ch , '(){}[].,;:?' ) ) {
156156 this . tokens . push ( { index : this . index , text : ch } ) ;
@@ -194,12 +194,49 @@ Lexer.prototype = {
194194 ch === '\n' || ch === '\v' || ch === '\u00A0' ) ;
195195 } ,
196196
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 ) {
198204 return ( 'a' <= ch && ch <= 'z' ||
199205 'A' <= ch && ch <= 'Z' ||
200206 '_' === ch || ch === '$' ) ;
201207 } ,
202208
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+
203240 isExpOperator : function ( ch ) {
204241 return ( ch === '-' || ch === '+' || this . isNumber ( ch ) ) ;
205242 } ,
@@ -248,12 +285,13 @@ Lexer.prototype = {
248285
249286 readIdent : function ( ) {
250287 var start = this . index ;
288+ this . index += this . peekMultichar ( ) . length ;
251289 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 ) ) {
254292 break ;
255293 }
256- this . index ++ ;
294+ this . index += ch . length ;
257295 }
258296 this . tokens . push ( {
259297 index : start ,
@@ -1183,7 +1221,13 @@ ASTCompiler.prototype = {
11831221 } ,
11841222
11851223 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+ }
11871231 } ,
11881232
11891233 computedMember : function ( left , right ) {
@@ -1748,6 +1792,7 @@ function $ParseProvider() {
17481792 'null' : null ,
17491793 'undefined' : undefined
17501794 } ;
1795+ var identStart , identContinue ;
17511796
17521797 /**
17531798 * @ngdoc method
@@ -1764,17 +1809,50 @@ function $ParseProvider() {
17641809 literals [ literalName ] = literalValue ;
17651810 } ;
17661811
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+
17671841 this . $get = [ '$filter' , function ( $filter ) {
17681842 var noUnsafeEval = csp ( ) . noUnsafeEval ;
17691843 var $parseOptions = {
17701844 csp : noUnsafeEval ,
17711845 expensiveChecks : false ,
1772- literals : copy ( literals )
1846+ literals : copy ( literals ) ,
1847+ isIdentifierStart : isFunction ( identStart ) && identStart ,
1848+ isIdentifierContinue : isFunction ( identContinue ) && identContinue
17731849 } ,
17741850 $parseOptionsExpensive = {
17751851 csp : noUnsafeEval ,
17761852 expensiveChecks : true ,
1777- literals : copy ( literals )
1853+ literals : copy ( literals ) ,
1854+ isIdentifierStart : isFunction ( identStart ) && identStart ,
1855+ isIdentifierContinue : isFunction ( identContinue ) && identContinue
17781856 } ;
17791857 var runningChecksEnabled = false ;
17801858
0 commit comments