Over on the Oslo forums there have been a few questions about how to interact with
M (specifically MSchema) programmatically. I’ve been doing this quite a bit creating PluralSight’s
Oslo course, as well as working on various samples.
What I’m going to do is create an in-memory representation of one or more M files
by using the M Compiler – which is clearly named as Compiler (it in the Microsoft.M
namespace). Compiler has a couple of methods, one is aptly named Compile, which I’m
not going to use in this sample. Compile creates a CompilationResults object that
not only has the in-memory parsed representation of the MSchema, it also has the in-memory
representation of the Database that would be created if you used mx.exe to deploy
the M to the database.
Instead I’m going to use Compiler.Parse, because I’m only interested in the M in-memory
representations at the moment (not the database objects). I need to reference three
assemblies to get this to work: System.Xaml.dll, System.DataFlow.dll, and Microsoft.M.Framework.dll.
To get Compiler to be happy, I have to pass it a CompilerOptions object which is essentially
the arguments to the compiler. I’m going to actually parse the provided M sources
in the SDK for the Repository itself (found in the OSLOInstallDir\Models directory
as raw M files with a M project thrown in as well).
string dir = @"C:\Program Files (x86)\Microsoft Oslo SDK 1.0\Models"; CompilerOptions cops = new CompilerOptions(); cops.IncludeStandardLibrary = true; foreach (string file in Directory.GetFiles(dir, "*.m", SearchOption.AllDirectories)) { CompilerInput cinput = new CompilerInput(); cinput.Name = Path.GetFileNameWithoutExtension(file); cinput.Reader = new StreamReader(file, Encoding.UTF8, true); cops.Sources.Add(cinput); } CompilationResults cresults = Compiler.Parse(cops);
After I get the CompilationResults back, I can loop and party on the CompilationResults.ParsedSources.Modules
collection. This will be the collection of MSchema modules found in the source directory.
To get at the actual M artifacts, for each Module I need to loop its Members property.
Members is of type ICollection
. IDeclaration is the interface that all Module members implement. If I use Reflector
(such a handy tool) I can see all the types that implement this interface:
I put a red arrow next to the two really interesting implementations – ExtentDeclaration
and TypeDeclaration. If I had the following M, I’d get one of each object:
module MFun
{
type foo : {
id : Integer32 = AutoNumber();
data : Text;
} where identity
id; foos : foo*; }
In this case foo is a TypeDeclaration, and foos is an ExtentDeclaration. This example
is pretty easy, but because MSchema is flexible, things can get ugly very quickly.
Where things get ugly is when you want to follow the trail further than just the top
level object. What if for example I wanted to find out the definition for each ExtentDeclaration.
Let me take another example:
module Models1 { Model : { Id : Integer64 = AutoNumber(); Name : Text; }* where identity Id; } module Models2 { Model :( { Id : Integer64 = AutoNumber(); Name : Text; } where identity Id)*; }
In this sample I’d have two Modules – each with a single ExtentDeclaration. But the
first ExtentDeclaration’s Type property is a ParamaterizedExpression. A ParameterizedExpression
has an Arguments collection, and that’s where you can get back to the actual definition.
In this case its ParameterizedExpression is made up of two arguments: one is a CollectionType
that shows me the {Id, Name} structure, and the other is another ParamaterizedExpression
that is the definition of the where statement. The second Module (Models2) has a single
ExtentDeclaration, but its Type is Collection (because of the parenthesis and the
way the MSchema is parsed). At the end of the day these two extents really are the
*same* semantically – but because of the slightly different syntax – the object model
ends up looking different.
If I where to parse my “foo” example, I’d end up with one Module with two members,
and if I wanted to figure out where the definition of the ExtentDeclaration came from,
I’d have to go down the object model until I found the type declaration for the collection
type that foos is made up of.
Here is some code (included in the project you can download at the end) that finds
the type name for every ExtentDeclaration in a Module, at least using all the various
M sources I have 🙂 (That is – it works on my machine).
public static string FindExtentTypeName(ExtentDeclaration extent) { string name = "Not Found"; var et = extent.Type as Microsoft.M.CollectionType; if (et != null) { if (et.ElementType is ParameterizedExpression) { var theExpression = et.ElementType as ParameterizedExpression; //check to see if expression refers to a type var results = (from arg in theExpression.Arguments where arg.GetType() == typeof(DeclarationReference) select arg); if (results != null && results.Count() > 0)//we have an extent { var dr = results.First() as DeclarationReference; if (dr != null) { name = dr.Name; } } else { name= extent.Name.Value; } }//direct extent if (et.ElementType is DeclarationReference) { name = ((DeclarationReference)et.ElementType).Name; } } else { var parm = extent.Type as ParameterizedExpression; if (parm != null) { CollectionType collectionType = (from ex in parm.Arguments where ex.GetType() == typeof(CollectionType) select ex).First() as CollectionType; if (collectionType.ElementType is EntityType) { name = extent.Name.Value; } if (collectionType.ElementType is DeclarationReference) { name = ((DeclarationReference)collectionType.ElementType).Name; } } } return name; } }
So the output looks like this:
Happy M parsing.
Check out my new book on REST.