@@ -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 ) {
@@ -1746,6 +1790,7 @@ function $ParseProvider() {
17461790 'null' : null ,
17471791 'undefined' : undefined
17481792 } ;
1793+ var identStart , identContinue ;
17491794
17501795 /**
17511796 * @ngdoc method
@@ -1762,17 +1807,50 @@ function $ParseProvider() {
17621807 literals [ literalName ] = literalValue ;
17631808 } ;
17641809
1810+ /**
1811+ * @ngdoc method
1812+ * @name $parseProvider#setIdentifierFns
1813+ * @description
1814+ *
1815+ * Allows defining the set of characters that are allowed in Angular expressions. The function
1816+ * `identifierStart` will get called to know if a given character is a valid character to be the
1817+ * first character for an identifier. The function `identifierContinue` will get called to know if
1818+ * a given character is a valid character to be a follow-up identifier character. The functions
1819+ * `identifierStart` and `identifierContinue` will receive as arguments the single character to be
1820+ * identifier and the character code point. These arguments will be `string` and `numeric`. Keep in
1821+ * mind that the `string` parameter can be two characters long depending on the character
1822+ * representation. It is expected for the function to return `true` or `false`, whether that
1823+ * character is allowed or not.
1824+ *
1825+ * Since this function will be called extensivelly, keep the implementation of these functions fast,
1826+ * as the performance of these functions have a direct impact on the expressions parsing speed.
1827+ *
1828+ * @param {function= } identifierStart The function that will decide whether the given character is
1829+ * a valid identifier start character.
1830+ * @param {function= } identifierContinue The function that will decide whether the given character is
1831+ * a valid identifier continue character.
1832+ */
1833+ this . setIdentifierFns = function ( identifierStart , identifierContinue ) {
1834+ identStart = identifierStart ;
1835+ identContinue = identifierContinue ;
1836+ return this ;
1837+ } ;
1838+
17651839 this . $get = [ '$filter' , function ( $filter ) {
17661840 var noUnsafeEval = csp ( ) . noUnsafeEval ;
17671841 var $parseOptions = {
17681842 csp : noUnsafeEval ,
17691843 expensiveChecks : false ,
1770- literals : copy ( literals )
1844+ literals : copy ( literals ) ,
1845+ isIdentifierStart : isFunction ( identStart ) && identStart ,
1846+ isIdentifierContinue : isFunction ( identContinue ) && identContinue
17711847 } ,
17721848 $parseOptionsExpensive = {
17731849 csp : noUnsafeEval ,
17741850 expensiveChecks : true ,
1775- literals : copy ( literals )
1851+ literals : copy ( literals ) ,
1852+ isIdentifierStart : isFunction ( identStart ) && identStart ,
1853+ isIdentifierContinue : isFunction ( identContinue ) && identContinue
17761854 } ;
17771855 var runningChecksEnabled = false ;
17781856
0 commit comments