picoe / Eto.Parse

Recursive descent LL(k) parser for .NET with Fluent API, BNF, EBNF and Gold Grammars
MIT License
148 stars 30 forks source link

Trouble with "or" parsing #42

Closed mennodeij closed 3 years ago

mennodeij commented 4 years ago

I'm trying to get a parser that allows for arguments to be given in arbitrary order, and I'm having some difficulty with this. Maybe I'm going at this completely the wrong way - please enlighten me if this is the case ;-)

I run into trouble if I want to "OR" together two parsers, one for each fragment.

Some code:

[Test]
public void TestEitherOrGrammar()
{
  var ws = -Terminals.WhiteSpace;
  var space = +Terminals.WhiteSpace;
  Parser eq = "=";

  var str = new StringParser { AllowNonQuoted = true };
  var @int = new NumberParser { AllowDecimal = false, AllowExponent = false, ValueType = typeof(int) };

  var width = "width" & eq & @int.Named("width");
  Grammar wGrammar = new Grammar(width);
  Assert.AreEqual(500, wGrammar.Match("width=500").Matches["width"].Value);

  var height = "height" & eq & @int.Named("height");
  Grammar hGrammar = new Grammar(height);
  Assert.AreEqual(600, hGrammar.Match("height=600").Matches["height"].Value);

  var wh = width & space & height;
  Grammar whGrammar = new Grammar(wh);
  Assert.AreEqual(700, whGrammar.Match("width=350 height=700").Matches["height"].Value);
  Assert.AreEqual(350, whGrammar.Match("width=350 height=700").Matches["width"].Value);

  var hw = height & space & width;
  Grammar hwGrammar = new Grammar(hw);
  Assert.AreEqual(700, hwGrammar.Match("height=700 width=350").Matches["height"].Value);
  Assert.AreEqual(350, hwGrammar.Match("height=700 width=350").Matches["width"].Value);

  // why is this not working
  var whhw = width | height;
  // var whhw = (wh|hw); //also not working

  var whhwGrammar = new Grammar(whhw);
  Assert.AreEqual(800, whhwGrammar.Match("width=400 height=800").Matches["height"].Value);
  Assert.AreEqual(400, whhwGrammar.Match("width=400 height=800").Matches["width"].Value);
  Assert.AreEqual(800, whhwGrammar.Match("height=800 width=400").Matches["height"].Value);
  Assert.AreEqual(400, whhwGrammar.Match("height=800 width=400").Matches["width"].Value);

}
cwensley commented 4 years ago

The problem you have is that you are trying to parse "width=400 height=800", which is not an or but a concatenation of the two.

So something like this would probably work if you only want width and height or height and width:

var whhw = (width & space & height) | (height & space & width);

mennodeij commented 4 years ago

I have tried that, but to no avail.

  // why are none of these working
  // var whhw = (width & height) | (height & width);
  var whhw = (width & space & height) | (height & space & width);
  // var whhw = width | height; // also not working
  // var whhw = (wh|hw); // also not working

  var whhwGrammar = new Grammar(whhw);
  var matches = whhwGrammar.Match("width=400 height=800");
  Assert.That(matches.HasMatches);
cwensley commented 3 years ago

Sorry for such late response on this, but I finally got some spare time and was playing around with this project a little.

The reason this is happening is because the sequence parsers for width and height are being reused when you use it in the next sequence you include them in.

For example, this turns it into this:

var width = "width" & eq & intParser.Named("width");
var height = "height" & eq & intParser.Named("height");

var whhw = (width & space & height) | (height & space & width);

// width is now ("width" & eq & intParser & space & height)
// height is now ("height" & eq & intParser & space & width)

To fix this, you need to use the Separate() or Named() extension, so they are treated as separate "rules" and won't be integrated in the other rules.

var width = ("width" & eq & intParser.Named("width")).Separate();
var height = ("height" & eq & intParser.Named("height")).Separate();

Hope this helps!

cwensley commented 3 years ago

By the way, one way to debug what the final structure of the grammar ends up with is by doing new Eto.Parse.Writers.DisplayParserWriter().Write(myGrammar)