Search code examples
pegpegjs

How do I DRY this PEGjs rule?


the following works just fine for what I'm trying to do, but it's obviously very repetitive. It should match the following examples:

  • #id.class1.class2 attr="asdsa"
  • .class1.class2 attr="asdsad"
  • attr="asds"

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;
}

Solution

  • 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]; }