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 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+ */
107+ Matcher . many = function ( required ) {
108+ var ms = Array . prototype . slice . call ( arguments , 1 ) . reduce ( function ( acc , v ) {
109+ if ( v . expand ) {
110+ // Insert all of the options for the given complex rule as
111+ // individual options.
112+ acc . push . apply ( acc , ValidationTypes . complex [ v . expand ] . options ) ;
113+ } else {
114+ acc . push ( Matcher . cast ( v ) ) ;
115+ }
116+ return acc ;
117+ } , [ ] ) ;
118+ if ( required === true ) { required = ms . map ( function ( ) { return true ; } ) ; }
119+ var result = new Matcher ( function ( expression ) {
120+ var seen = [ ] , i , j ;
121+ for ( i = 0 ; expression . hasNext ( ) && i < ms . length ; i ++ ) {
122+ for ( j = 0 ; j < ms . length ; j ++ ) {
123+ if ( ! seen [ j ] && ms [ j ] . match ( expression ) ) {
124+ seen [ j ] = true ;
125+ break ;
126+ }
127+ }
128+ if ( j === ms . length ) {
129+ break ;
130+ }
131+ }
132+ if ( required === false ) {
133+ return ( i > 0 ) ;
134+ }
135+ // Finer-grained specification of which are required.
136+ for ( i = 0 ; i < ms . length ; i ++ ) {
137+ if ( required [ i ] && ! seen [ i ] ) {
138+ return false ;
139+ }
140+ }
141+ return true ;
142+ } , function ( prec ) {
143+ var p = ( required === false ) ? Matcher . prec . OROR : Matcher . prec . ANDAND ;
144+ var s = ms . map ( function ( m , i ) {
145+ if ( required !== false && ! required [ i ] ) {
146+ return m . toString ( Matcher . prec . MOD ) + "?" ;
147+ }
148+ return m . toString ( p ) ;
149+ } ) . join ( required === false ? " || " : " && " ) ;
150+ if ( prec > p ) { s = "[ " + s + " ]" ; }
151+ return s ;
152+ } ) ;
153+ result . options = ms ;
154+ return result ;
155+ } ;
156+
157+ /**
158+ * Create a matcher for two or more options, where all options are
159+ * mandatory but they may appear in any order.
160+ */
161+ Matcher . andand = function ( ) {
162+ var args = Array . prototype . slice . call ( arguments ) ;
163+ args . unshift ( true ) ;
164+ return Matcher . many . apply ( Matcher , args ) ;
165+ } ;
166+
167+ /**
168+ * Create a matcher for two or more options, where options are
169+ * optional and may appear in any order, but at least one must be
170+ * present.
171+ */
172+ Matcher . oror = function ( ) {
173+ var args = Array . prototype . slice . call ( arguments ) ;
174+ args . unshift ( false ) ;
175+ return Matcher . many . apply ( Matcher , args ) ;
176+ } ;
177+
178+ /** Instance methods on Matchers. */
179+ Matcher . prototype = {
180+ constructor : Matcher ,
181+ // These are expected to be overridden in every instance.
182+ match : function ( expression ) { throw new Error ( "unimplemented" ) ; } ,
183+ toString : function ( ) { throw new Error ( "unimplemented" ) ; } ,
184+ // This returns a standalone function to do the matching.
185+ func : function ( ) { return this . match . bind ( this ) ; } ,
186+ // Basic combinators
187+ then : function ( m ) { return Matcher . seq ( this , m ) ; } ,
188+ or : function ( m ) { return Matcher . alt ( this , m ) ; } ,
189+ andand : function ( m ) { return Matcher . many ( true , this , m ) ; } ,
190+ oror : function ( m ) { return Matcher . many ( false , this , m ) ; } ,
191+ // Component value multipliers
192+ star : function ( ) { return this . braces ( 0 , Infinity , "*" ) ; } ,
193+ plus : function ( ) { return this . braces ( 1 , Infinity , "+" ) ; } ,
194+ question : function ( ) { return this . braces ( 0 , 1 , "?" ) ; } ,
195+ hash : function ( ) {
196+ return this . braces ( 1 , Infinity , "#" , Matcher . cast ( "," ) ) ;
197+ } ,
198+ braces : function ( min , max , marker , optSep ) {
199+ var m = this ;
200+ if ( ! marker ) {
201+ marker = "{" + min + "," + max + "}" ;
202+ }
203+ return new Matcher ( function ( expression ) {
204+ var result , i ;
205+ for ( i = 0 , result = true ; result && i < max && expression . hasNext ( ) ; ) {
206+ result = m . match ( expression ) ;
207+ if ( result ) {
208+ i ++ ;
209+ if ( optSep && i < max && expression . hasNext ( ) ) {
210+ expression . mark ( ) ;
211+ result = optSep . match ( expression ) ;
212+ if ( result && ! expression . hasNext ( ) ) {
213+ // Trailing separator, boo. Back up.
214+ expression . restore ( ) ;
215+ break ;
216+ } else {
217+ expression . drop ( ) ;
218+ }
219+ }
220+ }
221+ }
222+ return ( i >= min ) ;
223+ } , function ( ) { return m . toString ( Matcher . prec . MOD ) + marker ; } ) ;
224+ }
225+ } ;
226+
227+ ValidationTypes = {
4228
5229 isLiteral : function ( part , literals ) {
6230 var text = part . text . toString ( ) . toLowerCase ( ) ,
@@ -24,6 +248,13 @@ var ValidationTypes = {
24248 return ! ! this . complex [ type ] ;
25249 } ,
26250
251+ describe : function ( type ) {
252+ if ( this . complex [ type ] instanceof Matcher ) {
253+ return this . complex [ type ] . toString ( 0 ) ;
254+ }
255+ return type ;
256+ } ,
257+
27258 /**
28259 * Determines if the next part(s) of the given expression
29260 * are any of the given types.
@@ -72,6 +303,8 @@ var ValidationTypes = {
72303 if ( result ) {
73304 expression . next ( ) ;
74305 }
306+ } else if ( this . complex [ type ] instanceof Matcher ) {
307+ result = this . complex [ type ] . match ( expression ) ;
75308 } else {
76309 result = this . complex [ type ] ( expression ) ;
77310 }
0 commit comments