Here's a simple proof of concept in a jsFiddle. You basically just loop through the proposed regex in reverse and look for the longest sub-match.
Note: this has a known issue in that it doesn't always handle groups well. For example, it will say foo bar b
doesn't match foo( bar)+
at all, when it ought to say there's still hope. This could be fixed by getting much more creative with the following line:
temp_re = new RegExp(regex_part+'$'); // make sure this partial match ends in a piece that could still match the entire regex
Basically, you would need to parse the partial regex to see if it ends in a group, then check recursively for partial matches against that ending group. That's rather complicated, so I'm not getting into it for purposes of my demo.
JavaScript (using jQuery only for demo purposes):
var strings = {
matches: "Matches!",
stillhope: "There's still hope...",
mismatch: "Does not match",
empty: "No text yet"
};
// Object to handle watching for partial matches
function PartialRegexMonitor(regex, input_id) {
var self = this;
this.relen = regex.length;
$('body').on('keyup', '#'+input_id, function() {
self.update_test_results(regex, input_id);
});
}
PartialRegexMonitor.prototype.update_test_results = function(regex, input_id) {
var input = $('#'+input_id).val(),
matches = find_partial_matches(regex, input),
span = $('#'+input_id).siblings('.results');
span.removeClass('match');
span.removeClass('stillhope');
span.removeClass('mismatch');
span.removeClass('empty');
span.addClass(matches.type)
.html(strings[matches.type]);
}
// Test a partial regex against a string
function partial_match_tester(regex_part, str) {
var matched = false;
try {
var re = new RegExp(regex_part, 'g'),
matches = str.match(re),
match_count = matches.length,
temp_re;
for(var i = 0; i < match_count; i++) {
temp_re = new RegExp(regex_part+'$'); // make sure this partial match ends in a piece that could still match the entire regex
matched = temp_re.test(str);
if(matched) break;
}
}
catch(e) {
}
return matched;
}
// Find the longest matching partial regex
function find_partial_matches(regex, str) {
var relen = regex.length,
matched = false,
matches = {type: 'mismatch',
len: 0},
regex_part = '';
if(str.length == 0) {
matches.type = 'empty';
return matches;
}
for(var i=relen; i>=1; i--) {
if(i==1 && str[0] == '^') { // don't allow matching against only '^'
continue;
}
regex_part = regex.substr(0,i);
// replace {\d}$ with {0,\d} for testing
regex_part = regex_part.replace(/\{(\d)\}$/g, '{0,$1}');
matched = partial_match_tester(regex_part, str);
if(matched) {
matches.type = (i==relen ? 'matches' : 'stillhope');
console.log(matches.type + ": "+regex.substr(0,i)+" "+str);
matches.len = i;
break;
}
}
return matches;
}
// Demo
$(function() {
new PartialRegexMonitor('foo bar', 'input_0');
new PartialRegexMonitor('^foo bar$', 'input_1');
new PartialRegexMonitor('^fo+(\\s*b\\S[rz])+$', 'input_2');
new PartialRegexMonitor('^\\d{3}-\\d{3}-\\d{4}$', 'input_3');
});
HTML for the demo:
<p>
Test against <span class="regex">foo bar</span>:<br/>
<input type="text" id="input_0" />
<span class="results empty">No text yet</span>
</p>
<p>
Test against <span class="regex">^foo bar$</span>:<br/>
<input type="text" id="input_1" />
<span class="results empty">No text yet</span>
</p>
<p>
Test against <span class="regex">^fo+(\s*b\S[rz])+$</span> (e.g., "foo bar", "foo baz", "foo bar baz"):<br/>
<input type="text" id="input_2" />
<span class="results empty">No text yet</span>
</p>
<p>
Test against <span class="regex">^\d{3}-\d{3}-\d{4}$</span>:<br/>
<input type="text" id="input_3" />
<span class="results empty">No text yet</span>
</p>
CSS for the demo
.empty {
background-color: #eeeeee;
}
.matches {
background-color: #ccffcc;
font-weight: bold;
}
.stillhope {
background-color: #ccffff;
}
.mismatch {
background-color: #ffcccc;
font-weight: bold;
}
.regex {
border-top:1px solid #999;
border-bottom:1px solid #999;
font-family: Courier New, monospace;
background-color: #eee;
color: #666;
}
Sample screenshot from the demo
