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:
- the call to DynamicParser.LoadFromMgx(), which loads the compiled MGrammar code into the process
- 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:
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.