Skip to content

Commit 898751b

Browse files
committed
Add a simple parser to create Matcher objects from strings.
1 parent 3dc7595 commit 898751b

File tree

3 files changed

+94
-7
lines changed

3 files changed

+94
-7
lines changed

src/css/ValidationTypes.js

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//This file will likely change a lot! Very experimental!
2+
/*global StringReader*/
23
var ValidationTypes;
34

45
/**
@@ -34,6 +35,87 @@ Matcher.prec = {
3435
ALT: 1
3536
};
3637

38+
/** Simple recursive-descent grammar to build matchers from strings. */
39+
Matcher.parse = function(str) {
40+
var reader, eat, expr, oror, andand, seq, mod, term, result;
41+
reader = new StringReader(str);
42+
eat = function(matcher) {
43+
var result = reader.readMatch(matcher);
44+
if (result === null) {
45+
throw new SyntaxError(
46+
"Expected "+matcher, reader.getLine(), reader.getCol());
47+
}
48+
return result;
49+
};
50+
expr = function() {
51+
// expr = oror (" | " oror)*
52+
var m = [ oror() ];
53+
while (reader.readMatch(" | ") !== null) {
54+
m.push(oror());
55+
}
56+
return m.length === 1 ? m[0] : Matcher.alt.apply(Matcher, m);
57+
};
58+
oror = function() {
59+
// oror = andand ( " || " andand)*
60+
var m = [ andand() ];
61+
while (reader.readMatch(" || ") !== null) {
62+
m.push(andand());
63+
}
64+
return m.length === 1 ? m[0] : Matcher.oror.apply(Matcher, m);
65+
};
66+
andand = function() {
67+
// andand = seq ( " && " seq)*
68+
var m = [ seq() ];
69+
while (reader.readMatch(" && ") !== null) {
70+
m.push(seq());
71+
}
72+
return m.length === 1 ? m[0] : Matcher.andand.apply(Matcher, m);
73+
};
74+
seq = function() {
75+
// seq = mod ( " " mod)*
76+
var m = [ mod() ];
77+
while (reader.readMatch(/^ (?![&|\]])/) !== null) {
78+
m.push(mod());
79+
}
80+
return m.length === 1 ? m[0] : Matcher.seq.apply(Matcher, m);
81+
};
82+
mod = function() {
83+
// mod = term ( "?" | "*" | "+" | "#" | "{<num>,<num>}" )?
84+
var m = term();
85+
if (reader.readMatch("?") !== null) {
86+
return m.question();
87+
} else if (reader.readMatch("*") !== null) {
88+
return m.star();
89+
} else if (reader.readMatch("+") !== null) {
90+
return m.plus();
91+
} else if (reader.readMatch("#") !== null) {
92+
return m.hash();
93+
} else if (reader.readMatch(/^\{\s*/) !== null) {
94+
var min = eat(/^\d+/);
95+
eat(/^\s*,\s*/);
96+
var max = eat(/^\d+/);
97+
eat(/^\s*\}/);
98+
return m.braces(+min, +max);
99+
}
100+
return m;
101+
};
102+
term = function() {
103+
// term = <nt> | literal | "[ " expression " ]"
104+
if (reader.readMatch("[ ") !== null) {
105+
var m = expr();
106+
eat(" ]");
107+
return m;
108+
}
109+
return Matcher.fromType(eat(/^[^ ?*+#{]+/));
110+
};
111+
result = expr();
112+
if (!reader.eof()) {
113+
throw new SyntaxError(
114+
"Expected end of string", reader.getLine(), reader.getCol());
115+
}
116+
return result;
117+
};
118+
37119
/**
38120
* Convert a string to a matcher (parsing simple alternations),
39121
* or do nothing if the argument is already a matcher.
@@ -42,10 +124,7 @@ Matcher.cast = function(m) {
42124
if (m instanceof Matcher) {
43125
return m;
44126
}
45-
if (/ \| /.test(m)) {
46-
return Matcher.alt.apply(Matcher, m.split(" | "));
47-
}
48-
return Matcher.fromType(m);
127+
return Matcher.parse(m);
49128
};
50129

51130
/**

src/util/StringReader.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ StringReader.prototype = {
220220
* returns those characters. If a match is found, the row and column
221221
* are adjusted; if no match is found, the reader's state is unchanged.
222222
* reading or false to stop.
223-
* @param {String|RegExp} matchter If a string, then the literal string
223+
* @param {String|RegExp} matcher If a string, then the literal string
224224
* value is searched for. If a regular expression, then any string
225225
* matching the pattern is search for.
226226
* @return {String} The string made up of all characters that matched or
@@ -234,7 +234,7 @@ StringReader.prototype = {
234234

235235
//if it's a string, just do a straight match
236236
if (typeof matcher === "string"){
237-
if (source.indexOf(matcher) === 0){
237+
if (source.slice(0, matcher.length) === matcher){
238238
value = this.readCount(matcher.length);
239239
}
240240
} else if (matcher instanceof RegExp){

tests/util/StringReader.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@
244244
var result = reader.readMatch("Hello");
245245

246246
Assert.areEqual("Hello", result);
247+
248+
result = reader.readMatch("Good-bye!");
249+
250+
Assert.isNull(result, "Should return null if no match.");
247251
},
248252

249253
/*
@@ -253,9 +257,13 @@
253257
var testString = "Hello world!",
254258
reader = new StringReader(testString);
255259

256-
var result = reader.readMatch(/^Hello/);
260+
var result = reader.readMatch(/^Hello?/);
257261

258262
Assert.areEqual("Hello", result);
263+
264+
result = reader.readMatch(/^ war/);
265+
266+
Assert.isNull(result, "Should return null if no match.");
259267
}
260268

261269

0 commit comments

Comments
 (0)