11//This file will likely change a lot! Very experimental!
2- /*global ValidationError */
3- var ValidationTypes = {
2+ var ValidationTypes ;
3+
4+ /**
5+ * This class implements a combinator library for matcher functions.
6+ * The combinators are described at:
7+ * https://developer.mozilla.org/en-US/docs/Web/CSS/Value_definition_syntax#Component_value_combinators
8+ */
9+ var Matcher = function ( matchFunc , toString ) {
10+ this . match = function ( expression ) {
11+ // Save/restore marks to ensure that failed matches always restore
12+ // the original location in the expression.
13+ var result ;
14+ expression . mark ( ) ;
15+ result = matchFunc ( expression ) ;
16+ if ( result ) {
17+ expression . drop ( ) ;
18+ } else {
19+ expression . restore ( ) ;
20+ }
21+ return result ;
22+ } ;
23+ this . toString = typeof toString === "function" ? toString : function ( ) {
24+ return toString ;
25+ } ;
26+ } ;
27+
28+ /** Precedence table of combinators. */
29+ Matcher . prec = {
30+ MOD : 5 ,
31+ SEQ : 4 ,
32+ ANDAND : 3 ,
33+ OROR : 2 ,
34+ ALT : 1
35+ } ;
36+
37+ /**
38+ * Convert a string to a matcher (parsing simple alternations),
39+ * or do nothing if the argument is already a matcher.
40+ */
41+ Matcher . cast = function ( m ) {
42+ if ( m instanceof Matcher ) {
43+ return m ;
44+ }
45+ if ( / \| / . test ( m ) ) {
46+ return Matcher . alt . apply ( Matcher , m . split ( " | " ) ) ;
47+ }
48+ return Matcher . fromType ( m ) ;
49+ } ;
50+
51+ /**
52+ * Create a matcher for a single type.
53+ */
54+ Matcher . fromType = function ( type ) {
55+ return new Matcher ( function ( expression ) {
56+ return expression . hasNext ( ) && ValidationTypes . isType ( expression , type ) ;
57+ } , type ) ;
58+ } ;
59+
60+ /**
61+ * Create a matcher for one or more juxtaposed words, which all must
62+ * occur, in the given order.
63+ */
64+ Matcher . seq = function ( ) {
65+ var ms = Array . prototype . slice . call ( arguments ) . map ( Matcher . cast ) ;
66+ if ( ms . length === 1 ) { return ms [ 0 ] ; }
67+ return new Matcher ( function ( expression ) {
68+ var i , result = true ;
69+ for ( i = 0 ; result && i < ms . length ; i ++ ) {
70+ result = ms [ i ] . match ( expression ) ;
71+ }
72+ return result ;
73+ } , function ( prec ) {
74+ var p = Matcher . prec . SEQ ;
75+ var s = ms . map ( function ( m ) { return m . toString ( p ) ; } ) . join ( " " ) ;
76+ if ( prec > p ) { s = "[ " + s + " ]" ; }
77+ return s ;
78+ } ) ;
79+ } ;
80+
81+ /**
82+ * Create a matcher for one or more alternatives, where exactly one
83+ * must occur.
84+ */
85+ Matcher . alt = function ( ) {
86+ var ms = Array . prototype . slice . call ( arguments ) . map ( Matcher . cast ) ;
87+ if ( ms . length === 1 ) { return ms [ 0 ] ; }
88+ return new Matcher ( function ( expression ) {
89+ var i , result = false ;
90+ for ( i = 0 ; ! result && i < ms . length ; i ++ ) {
91+ result = ms [ i ] . match ( expression ) ;
92+ }
93+ return result ;
94+ } , function ( prec ) {
95+ var p = Matcher . prec . ALT ;
96+ var s = ms . map ( function ( m ) { return m . toString ( p ) ; } ) . join ( " | " ) ;
97+ if ( prec > p ) { s = "[ " + s + " ]" ; }
98+ return s ;
99+ } ) ;
100+ } ;
101+
102+ /**
103+ * Create a matcher for two or more options. This implements the
104+ * double bar (||) and double ampersand (&&) operators, as well as
105+ * variants of && where some of the alternatives are optional.
106+ * This will backtrack through even successful matches to try to
107+ * maximize the number of items matched.
108+ */
109+ Matcher . many = function ( required ) {
110+ var ms = Array . prototype . slice . call ( arguments , 1 ) . reduce ( function ( acc , v ) {
111+ if ( v . expand ) {
112+ // Insert all of the options for the given complex rule as
113+ // individual options.
114+ acc . push . apply ( acc , ValidationTypes . complex [ v . expand ] . options ) ;
115+ } else {
116+ acc . push ( Matcher . cast ( v ) ) ;
117+ }
118+ return acc ;
119+ } , [ ] ) ;
120+ if ( required === true ) { required = ms . map ( function ( ) { return true ; } ) ; }
121+ var result = new Matcher ( function ( expression ) {
122+ var seen = [ ] , max = 0 , pass = 0 ;
123+ var success = function ( matchCount ) {
124+ if ( pass === 0 ) {
125+ max = Math . max ( matchCount , max ) ;
126+ return matchCount === ms . length ;
127+ } else {
128+ return matchCount === max ;
129+ }
130+ } ;
131+ var tryMatch = function ( matchCount ) {
132+ for ( var i = 0 ; i < ms . length ; i ++ ) {
133+ if ( seen [ i ] ) { continue ; }
134+ expression . mark ( ) ;
135+ if ( ms [ i ] . match ( expression ) ) {
136+ seen [ i ] = true ;
137+ // Increase matchCount iff this was a required element
138+ // (or if all the elements are optional)
139+ if ( tryMatch ( matchCount + ( ( required === false || required [ i ] ) ? 1 : 0 ) ) ) {
140+ expression . drop ( ) ;
141+ return true ;
142+ }
143+ // Backtrack: try *not* matching using this rule, and
144+ // let's see if it leads to a better overall match.
145+ expression . restore ( ) ;
146+ seen [ i ] = false ;
147+ } else {
148+ expression . drop ( ) ;
149+ }
150+ }
151+ return success ( matchCount ) ;
152+ } ;
153+ if ( ! tryMatch ( 0 ) ) {
154+ // Couldn't get a complete match, retrace our steps to make the
155+ // match with the maximum # of required elements.
156+ pass ++ ;
157+ tryMatch ( 0 ) ;
158+ }
159+
160+ if ( required === false ) {
161+ return ( max > 0 ) ;
162+ }
163+ // Use finer-grained specification of which matchers are required.
164+ for ( var i = 0 ; i < ms . length ; i ++ ) {
165+ if ( required [ i ] && ! seen [ i ] ) {
166+ return false ;
167+ }
168+ }
169+ return true ;
170+ } , function ( prec ) {
171+ var p = ( required === false ) ? Matcher . prec . OROR : Matcher . prec . ANDAND ;
172+ var s = ms . map ( function ( m , i ) {
173+ if ( required !== false && ! required [ i ] ) {
174+ return m . toString ( Matcher . prec . MOD ) + "?" ;
175+ }
176+ return m . toString ( p ) ;
177+ } ) . join ( required === false ? " || " : " && " ) ;
178+ if ( prec > p ) { s = "[ " + s + " ]" ; }
179+ return s ;
180+ } ) ;
181+ result . options = ms ;
182+ return result ;
183+ } ;
184+
185+ /**
186+ * Create a matcher for two or more options, where all options are
187+ * mandatory but they may appear in any order.
188+ */
189+ Matcher . andand = function ( ) {
190+ var args = Array . prototype . slice . call ( arguments ) ;
191+ args . unshift ( true ) ;
192+ return Matcher . many . apply ( Matcher , args ) ;
193+ } ;
194+
195+ /**
196+ * Create a matcher for two or more options, where options are
197+ * optional and may appear in any order, but at least one must be
198+ * present.
199+ */
200+ Matcher . oror = function ( ) {
201+ var args = Array . prototype . slice . call ( arguments ) ;
202+ args . unshift ( false ) ;
203+ return Matcher . many . apply ( Matcher , args ) ;
204+ } ;
205+
206+ /** Instance methods on Matchers. */
207+ Matcher . prototype = {
208+ constructor : Matcher ,
209+ // These are expected to be overridden in every instance.
210+ match : function ( expression ) { throw new Error ( "unimplemented" ) ; } ,
211+ toString : function ( ) { throw new Error ( "unimplemented" ) ; } ,
212+ // This returns a standalone function to do the matching.
213+ func : function ( ) { return this . match . bind ( this ) ; } ,
214+ // Basic combinators
215+ then : function ( m ) { return Matcher . seq ( this , m ) ; } ,
216+ or : function ( m ) { return Matcher . alt ( this , m ) ; } ,
217+ andand : function ( m ) { return Matcher . many ( true , this , m ) ; } ,
218+ oror : function ( m ) { return Matcher . many ( false , this , m ) ; } ,
219+ // Component value multipliers
220+ star : function ( ) { return this . braces ( 0 , Infinity , "*" ) ; } ,
221+ plus : function ( ) { return this . braces ( 1 , Infinity , "+" ) ; } ,
222+ question : function ( ) { return this . braces ( 0 , 1 , "?" ) ; } ,
223+ hash : function ( ) {
224+ return this . braces ( 1 , Infinity , "#" , Matcher . cast ( "," ) ) ;
225+ } ,
226+ braces : function ( min , max , marker , optSep ) {
227+ var m1 = this , m2 = optSep ? optSep . then ( this ) : this ;
228+ if ( ! marker ) {
229+ marker = "{" + min + "," + max + "}" ;
230+ }
231+ return new Matcher ( function ( expression ) {
232+ var result = true , i ;
233+ for ( i = 0 ; i < max ; i ++ ) {
234+ if ( i > 0 && optSep ) {
235+ result = m2 . match ( expression ) ;
236+ } else {
237+ result = m1 . match ( expression ) ;
238+ }
239+ if ( ! result ) { break ; }
240+ }
241+ return ( i >= min ) ;
242+ } , function ( ) { return m1 . toString ( Matcher . prec . MOD ) + marker ; } ) ;
243+ }
244+ } ;
245+
246+ ValidationTypes = {
4247
5248 isLiteral : function ( part , literals ) {
6249 var text = part . text . toString ( ) . toLowerCase ( ) ,
@@ -24,6 +267,13 @@ var ValidationTypes = {
24267 return ! ! this . complex [ type ] ;
25268 } ,
26269
270+ describe : function ( type ) {
271+ if ( this . complex [ type ] instanceof Matcher ) {
272+ return this . complex [ type ] . toString ( 0 ) ;
273+ }
274+ return type ;
275+ } ,
276+
27277 /**
28278 * Determines if the next part(s) of the given expression
29279 * are any of the given types.
@@ -72,6 +322,8 @@ var ValidationTypes = {
72322 if ( result ) {
73323 expression . next ( ) ;
74324 }
325+ } else if ( this . complex [ type ] instanceof Matcher ) {
326+ result = this . complex [ type ] . match ( expression ) ;
75327 } else {
76328 result = this . complex [ type ] ( expression ) ;
77329 }
0 commit comments