the following works just fine for what I'm trying to do, but it's obviously very repetitive. It should match the following examples:
It's tempting to use
id:idShortcut? classes:classShortcut* attrs:fullAttribute*
But I don't want it to match if all three components are absent. How do I specify a rule of 3 optional components, but at least one must be present?
attributes = id:idShortcut classes:classShortcut* attrs:fullAttribute*
{
var ret = [['id', id]];
for(var i = 0; i < classes.length; ++i ) {
ret.push(['class', classes[i]]);
}
for(var i = 0; i < attrs.length; ++i ) {
ret.push(attrs[i]);
}
return ret;
}
/ classes:classShortcut+ attrs:fullAttribute* {
// TODO: how to dry this with the above?
var ret = [];
for(var i = 0; i < classes.length; ++i ) {
ret.push(['class', classes[i]]);
}
for(var i = 0; i < attrs.length; ++i ) {
ret.push(attrs[i]);
}
return ret;
}
/ attrs:fullAttribute+ {
var ret = [];
for(var i = 0; i < attrs.length; ++i ) {
ret.push(attrs[i]);
}
return ret;
}
OK, not sure why this was even a struggle for me, but this refactored nicely as follows (and could probably be improved beyond that:
attributes
= a:( attributesAtLeastID
/ attributesAtLeastClass
/ attributesAtFullAttr )
{
var id = a[0];
var classes = a[1];
var attrs = a[2];
var ret = [];
if(id) { ret.push(['id', id]); }
for(var i = 0; i < classes.length; ++i ) {
ret.push(['class', classes[i]]);
}
for(var i = 0; i < attrs.length; ++i ) {
ret.push(attrs[i]);
}
}
attributesAtLeastID = id:idShortcut classes:classShortcut* attrs:fullAttribute* { return [id, classes, attrs]; }
attributesAtLeastClass = classes:classShortcut+ attrs:fullAttribute* { return [null, classes, attrs]; }
attributesAtFullAttr = attrs:fullAttribute+ { return [null, [], attrs]; }