Processing MGrammar Graphs in C#

In this post, I began a discussion of a simple language defined in MGrammar that allows the specification of two-dimensional lines. Through this language, I can write input files that look like this:

   1: line LineA (4,3) (2,5); 
   2: line LineB (5,6) (5,1);

Now what? I’ve got a grammar, and an input language … but how is the input language processed? As it turns out, the abstract syntax tree (AST) graph that MGrammar generates from the input source can be read from a .NET  language. In this post, I will illustrate how this is done.

The goal for this example is to take the source text and turn it into .NET objects. To accomplish this, I defined a Point class to contain the point data:

   1: internal class Point
   2: {
   3:     public int X { get; internal set; }
   4:     public int Y { get; internal set; }
   5: }

I also defined a Line class to contain the line data:

   1: internal class Line
   2: {
   3:     public string Name { get; internal set; }
   4:     public Point Point1 { get; internal set; }
   5:     public Point Point2 { get; internal set; }
   6: }

I then defined a private list to contain the data for the lines that I read in from the source language:

   1: private List<Line> thisLines;

My code, which is configured as a simple console application, begins as follows:

   1: string ImageFileName = @"..\..\..\Grammar\JeffFerguson.Calculatix.mgx";
   2: string LanguageName = "JeffFerguson.Calculatix.Grammars.Calculus";
   3:  
   4: try
   5: {
   6:     if (args.Length != 1)
   7:     {
   8:         Console.WriteLine("usage: lines [input]");
   9:         return;
  10:     }
  11:     thisLines = new List<Line>();
  12:     StreamReader InputReader = File.OpenText(args[0]);
  13:     DynamicParser Lang = DynamicParser.LoadFromMgx(ImageFileName, LanguageName);
  14:     if (Lang == null)
  15:         throw new NullReferenceException("LoadFromMgx() returned null");
  16:     object Root = Lang.Parse<object>(null, InputReader, ErrorReporter.Standard);
  17:     EnumerateGraph(new GraphBuilder(), Root);
  18:     ShowLines();
  19: }
  20: catch (Exception e)
  21: {
  22:     Console.WriteLine("*** EXCEPTION: {0}", e.Message);
  23: }
  24: finally
  25: {
  26:     Console.WriteLine();
  27:     Console.WriteLine("Press [Enter].");
  28:     Console.ReadLine();
  29: }

The Olso-related keys here are:

  1. the call to DynamicParser.LoadFromMgx(), which loads the compiled MGrammar code into the process
  2. the call to Lang.Parse(), which processes the source language and produces the AST graph

Once this is done, I call a private method called EnumerateGraph() which navigates the graph that the call to Parse() created. That implementation looks like this:

   1: private void EnumerateGraph(GraphBuilder builder, object node)
   2: {
   3:     Identifier Label = builder.GetLabel(node) as Identifier;
   4:     if (Label.Text.Equals("Line") == true)
   5:         ProcessLine(builder, node);
   6:     else
   7:     {
   8:         foreach (object ChildNode in builder.GetSequenceElements(node))
   9:         {
  10:             if((ChildNode is string) == false)
  11:                 EnumerateGraph(builder, ChildNode);
  12:         }
  13:     }
  14: }

Remember, the goal here is to read in the source input and create a collection of Line objects that represent the input. This code is moving down the tree, looking for nodes labeled “Line”. If the node is found, then another private method called ProcessLine() is called. If the current graph node does not have a label of “Line”, then the code checks siblings, and, through recursion, its children.

The implementation of ProcessLine() is pretty straightforward:

   1: private void ProcessLine(GraphBuilder builder, object LineNode)
   2: {
   3:     if (builder.GetSequenceCount(LineNode) != 3)
   4:         throw new NotSupportedException("Line does not have three children");
   5:     Line NewLine = new Line();
   6:     NewLine.Name = builder.GetSequenceElementAt(LineNode, 0) as string;
   7:     NewLine.Point1 = ProcessPoint(builder, builder.GetSequenceElementAt(LineNode, 1));
   8:     NewLine.Point2 = ProcessPoint(builder, builder.GetSequenceElementAt(LineNode, 2));
   9:     thisLines.Add(NewLine);
  10: }

Remember that the Line node should have three elements:

  • the line identifier
  • the starting point for the line
  • the ending point for the line

Recall that this part of the graph looks like this:

 

image

The implementation of ProcessLine() creates a new Line object and populates it with the values stored in the graph. The points are read in by another helper method called ProcessPoint(), which looks very similar:

   1: private Point ProcessPoint(GraphBuilder builder, object PointNode)
   2: {
   3:     if (builder.GetSequenceCount(PointNode) != 2)
   4:         throw new NotSupportedException("Point does not have two children");
   5:     Point NewPoint = new Point();
   6:     NewPoint.X = Convert.ToInt32(builder.GetSequenceElementAt(PointNode, 0) as string);
   7:     NewPoint.Y = Convert.ToInt32(builder.GetSequenceElementAt(PointNode, 1) as string);
   8:     return NewPoint;
   9: }

Once all of this is done, control returns to the caller, and a private method called ShowLines() displays the .NET Line objects created from the tree traversal out to the console. This couldn’t be easier:

   1: private void ShowLines()
   2: {
   3:     foreach (Line CurrentLine in thisLines)
   4:         Console.WriteLine("The line named {0} runs from ({1}, {2}) to ({3}, {4}).", CurrentLine.Name, CurrentLine.Point1.X, CurrentLine.Point1.Y, CurrentLine.Point2.X, CurrentLine.Point2.Y);
   5: }

This results in the following output being sent to the console:

The line named LineA runs from (4, 3) to (2, 5).
The line named LineB runs from (5, 6) to (5, 1).

Press [Enter].

And, there you have it: a language defined in MGrammar with an input source file processed by C# to produce results.

Print | posted @ Monday, March 16, 2009 3:06 PM

Comments on this entry:

Gravatar # re: Processing MGrammar Graphs in C#
by Sam at 3/21/2009 4:53 PM

Should your grammar be able to save you from the SequenceCount checks and from reading X and Y as string then converting to int?

I'm trying to do less work by using MGrammar, I think this is one of the places that's safe to do so...those checks should always pass if the parse succeeds.

Please continue this series, I envy your writing.
Gravatar # re: Processing MGrammar Graphs in C#
by Jeff Ferguson at 3/21/2009 5:45 PM

Thank you for the feedback, Sam!

You're right: the current code is a two-step process:

* read the node value as a string
* convert the string to an int

There is, if I understand it correctly, a way to do this all in one step. That is done by building a custom graph builder class that implements an interface called IGraphBuilder. Once I figure out its mechanics, I will provide a blog post with an implementation.
Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification:
 
 
Twitter