Some of my Code Concepts articles and Hands-on programming posts are building toward something bigger: a small compiler and parser of sorts--a simple Reverse Polish Notation calculator.
In this column, I integrate a few of the pieces together.
I'll show how to create a generic class that can be retrieved by name and a class that inherits from the generic class and pre-loads itself with functions stored in an external configuration at compile time.
In case you missed my articles about T4 templates and lambda expressions, I suggest reviewing them before reading this article if you are not familiar with those ideas.
Note: This blog post is also available as a PDF download.
Example
Our generic class is pretty easy to write. Internally, it will use a Dictionary object to hold the functions and support a few simple methods (Add(), Remove(), Get(), and an indexer). Here's the code:
public class FuncLibrary
{
private Dictionary
public Func
{
get
{
return _functions[name];
}
set
{
_functions[name] = value;
}
}
public void Add(string name, Func
{
_functions.Add(name, function);
}
public bool Remove(string name)
{
return _functions.Remove(name);
}
public Func
{
return this[name];
}
public FuncLibrary()
{
_functions = new Dictionary
}
}
You might be wondering why the first type is T1 and not T; you may also notice that the functions it stores can only accept one parameter. I call the type T1 because it is an exercise for you to copy/paste the code into a new class that accepts two parameters and make the needed changes. For my purposes, I made five versions, accepting from 0 to 4 parameters.
Now that we have a useful, generic class, it is time to look at our specific needs. For our RPN calculator, I decided that it will work in a fairly Lisp-like fashion, operating entirely on lists. As a result, all of the function inputs and outputs will be List
public class Functions : FuncLibrary
}
So far, so good. But, how will we pre-load this library with functions at compile time from an external source? This is where the T4 templates come into play.
First, we will make a simple XML file that can hold our functionality. Here is a truncated version of that XML file:
for (int counter = 1; counter <= input.Count - 1; counter++) {
result += input[counter];
}
return new List
]]>
If you look at the code within the CDATA block, you may wonder where the variable input came from--that is the name our system will give to the input to each function when it creates the lambda expressions. Remember, the input is of type List
It should start with a number of directives to declare the language as C# 3.5 (if you just declare it as C#, we won't have access to things like var, which would be a problem for the LINQ), reference the needed assemblies and load the namespaces the T4 code will need:
<#@ template language="C#v3.5″ #>
<#@ assembly name = "System.Core" #>
<#@ assembly name = "System.Xml" #>
<#@ assembly name = "System.Xml.Linq" #>
<#@ import namespace = "System" #>
<#@ import namespace = "System.Linq" #>
<#@ import namespace = "System.Xml" #>
<#@ import namespace = "System.Xml.Linq" #>
<#@ import namespace = "System.IO" #>
Next, we put in the body of our class, including any using statements that the defined functions might use:
public class Functions : FuncLibrary
public Functions() {
}
}
Now we need to fill in the constructor, but put T4 code in it that does the heavy lifting:
<#
var functionListLocation = @"C:\Users\jjames\Documents\Source Control Working Copy\TitaniumCrowbarUtilities\ExtendableRPNCalculator\FunctionList.xml";
var rawXml = File.ReadAllText(functionListLocation);
var functionList = XDocument.Parse(rawXml);
var functions = from function in functionList.Root.Elements() select function;
foreach (var item in functions)
{
var funcName = item.Attribute("name").Value;
var lambdaName = "lambda_" + funcName;
this.Write("Func
this.Write(item.Value);
this.Write("};\n");
this.Write("Add(\"" + funcName + "\", " + lambdaName + ");\n");
}
#>
The first line of code is the hardcoded path to the XML file; I haven't found a good way around this yet. Next, we read in the XML file and load it into an XDocument object. We iterate through each of the function elements in the XML document, and for each one, we extract the name attribute and store it in the funcName variable.
We then create a variable name for the generated code to hold the new lambda expression in, making sure that it will be unique by prefixing it with lambda_ (ugly, I know). Then we write to the output stream, first defining the lambda expressions by outputting a variable declaration and creating a lambda, then writing the contents of the function element, and finally, closing up the lambda definition and ending the variable declaration.
The last thing we do is call the class's Add() method with the new function's name and the lambda expression. The T4 template gets run any time we tell Visual Studio to "Save", even if the contents have not changed. Go ahead and do so, and take a look at the output (for me, it gets put into Functions1.cs since I named my template file Functions.cs):
using System;
using System.Collections.Generic;
using FunctionLibrary;
public class Functions : FuncLibrary
public Functions() {
Func
var result = input[0];
for (int counter = 1; counter <= input.Count - 1; counter++) {
result += input[counter];
}
return new List
};
Add(”add”, lambda_add);
}
}
Pretty neat, huh? If it doesn't seem particularly useful at the moment, consider the possibilities. The code could be stored in a database, allowing you to maintain a system where customer-specific modifications can be loaded at compile time without needing to touch to core code.
You could also create a Domain Specific Language (DSL) for the code in the configuration, allowing business users or other specialists to create the functionality that gets turned into actual .NET code when you compile everything together. The possibilities are limitless, and soon we will have a parser to start making calls to our function library.