1
1
//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 = {
4
247
5
248
isLiteral : function ( part , literals ) {
6
249
var text = part . text . toString ( ) . toLowerCase ( ) ,
@@ -24,6 +267,13 @@ var ValidationTypes = {
24
267
return ! ! this . complex [ type ] ;
25
268
} ,
26
269
270
+ describe : function ( type ) {
271
+ if ( this . complex [ type ] instanceof Matcher ) {
272
+ return this . complex [ type ] . toString ( 0 ) ;
273
+ }
274
+ return type ;
275
+ } ,
276
+
27
277
/**
28
278
* Determines if the next part(s) of the given expression
29
279
* are any of the given types.
@@ -72,6 +322,8 @@ var ValidationTypes = {
72
322
if ( result ) {
73
323
expression . next ( ) ;
74
324
}
325
+ } else if ( this . complex [ type ] instanceof Matcher ) {
326
+ result = this . complex [ type ] . match ( expression ) ;
75
327
} else {
76
328
result = this . complex [ type ] ( expression ) ;
77
329
}
0 commit comments