Wednesday, November 18, 2009

DynamicObject over M values

If you've used our bits, then for sure you have played with the DSL features.

If you haven't, it lets you create a DSL to process text and produce structured data.

With dynamic object support in .Net 4.0, we can add the ability to interact with that data as if it were objects in the CLR.

The code below is just such a little library that lets you do so. Don wrote it last night to help out with our PDC talk today.

Try it out!

// implementation of .NET 4.0 dynamic object over Node/Edge data model

// provides a read-only object-based view over M values

namespace Microsoft.M

{

using System.Reflection;

using System.Dataflow;

using System.IO;

using System.Dynamic;

using System;

using System.Collections;

using System.Collections.Generic;

public class DynamicObjectNode : DynamicObject

{

Node node;

// we never want to double-wrap, so all construction

// goes through NodeToObject, which unwraps atomics

internal DynamicObjectNode(Node node)

{

this.node = node;

}

// this method is the only way to go from the low-level

// node world to the world of (dynamic) objects

public static object NodeToObject(Node node)

{

if (node.NodeKind == NodeKind.Atomic)

return node.AtomicValue;

else

return new DynamicObjectNode(node);

}

public static IEnumerable<dynamic> ReadValues(TextReader reader)

{

foreach (var edge in Node.ReadFrom(reader))

{

yield return NodeToObject(edge.Node);

}

}

public static IEnumerable<dynamic> ReadValuesFromString(string text)

{

foreach (var edge in Node.ReadFromString(text))

{

yield return NodeToObject(edge.Node);

}

}

// implement the obvious three...

public override bool Equals(object obj)

{

return node.Equals(obj);

}

public override int GetHashCode()

{

return node.GetHashCode();

}

public override string ToString()

{

return node.ToString();

}

public Identifier GetBrand() { return node.Brand; }

// special case conversion to IEnumerable to support foreach

// over lists and collections

public override bool TryConvert(ConvertBinder binder, out object result)

{

result = null;

if (binder.Type != typeof(IEnumerable))

return false;

switch (node.NodeKind)

{

case NodeKind.Collection:

result = WrapCollectionNodeAsEnumerable(node);

return true;

case NodeKind.List:

result = WrapListNodeAsEnumerable(node);

return true;

default:

return false;

}

}

IEnumerable WrapCollectionNodeAsEnumerable(Node node)

{

foreach (var item in node.Edges)

{

yield return NodeToObject(item.Node);

}

}

IEnumerable WrapListNodeAsEnumerable(Node node)

{

foreach (var item in node.ViewAsList())

{

yield return NodeToObject(item);

}

}

// support [int] over list and [string] over records

public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)

{

result = null;

if (indexes.Length > 1)

return false;

if (node.NodeKind == NodeKind.List && indexes[0] is int)

{

int index = (int)indexes[0];

var list = node.ViewAsList();

if (index >= list.Count)

throw new IndexOutOfRangeException("Indexed past end of M list node");

result = NodeToObject(list[index]);

return true;

}

string name = indexes[0] as string;

if (name != null)

{

return TryGetMember(name, true, out result);

}

return false;

}

// support . over records and .Count/.IsReadOnly over collections/lists

public override bool TryGetMember(GetMemberBinder binder, out object result)

{

return TryGetMember(binder.Name, binder.IgnoreCase, out result);

}

bool TryGetMember(string name, bool ignoreCase, out object result)

{

result = null;

switch (node.NodeKind)

{

case NodeKind.List:

if (name == "Count")

{

result = node.ViewAsList().Count;

return true;

}

else if (name == "IsReadOnly")

{

result = true;

return true;

}

return false;

case NodeKind.Collection:

if (name == "Count")

{

result = node.Edges.Count;

return true;

}

else if (name == "IsReadOnly")

{

result = true;

return true;

}

return false;

case NodeKind.Record:

if (node.Edges.ContainsLabel(name))

{

result = NodeToObject(node.Edges[name]);

return true;

}

else if (ignoreCase)

{

foreach (var e in node.Edges)

{

if (string.Compare(e.Label.Text, name, StringComparison.InvariantCultureIgnoreCase) == 0)

{

result = NodeToObject(e.Node);

return true;

}

}

}

return false;

default:

return false;

}

}

}

public class Language

{

MImage image;

GraphStore store = new DefaultGraphStore();

string moduleName, languageName;

ParserFactory factory;

internal Language(MImage image, string moduleName, string languageName)

{

this.image = image;

this.moduleName = moduleName;

this.languageName = languageName;

this.factory = this.image.ParserFactories[moduleName + "." + languageName];

}

internal Language(Assembly assm, string moduleName, string languageName)

: this(MImage.LoadFromAssembly(assm), moduleName, languageName)

{

}

public MImage Image { get { return this.image; } }

public string ModuleName { get { return moduleName; } }

public string LanguageName { get { return languageName; } }

public static Language Load(MImage image, string moduleName, string languageName)

{

return new Language(image, moduleName, languageName);

}

public static Language Load(Assembly assm, string moduleName, string languageName)

{

return new Language(assm, moduleName, languageName);

}

public static Language LoadFromCurrentAssembly(string moduleName, string languageName)

{

var assm = Assembly.GetCallingAssembly();

return new Language(assm, moduleName, languageName);

}

public dynamic ParseString(string text)

{

return Parse(new StringReader(text));

}

public dynamic ParseString(string text, ErrorReporter errors)

{

return Parse(new StringReader(text), errors);

}

public dynamic ParseString(string text, ErrorReporter errors, string fileName)

{

return Parse(new StringReader(text), errors, fileName);

}

public dynamic ParseString(string text, ErrorReporter errors, string fileName, SourcePoint startLocation)

{

return Parse(new StringReader(text), errors, fileName, startLocation);

}

public dynamic Parse(TextReader reader)

{

return Parse(new TextReaderTextStream(reader), ErrorReporter.Standard);

}

public dynamic Parse(TextReader reader, ErrorReporter errors)

{

return Parse(new TextReaderTextStream(reader), errors);

}

public dynamic Parse(TextReader reader, ErrorReporter errors, string fileName, SourcePoint startLocation)

{

return Parse(new TextReaderTextStream(fileName, reader), errors, startLocation);

}

public dynamic Parse(TextReader reader, ErrorReporter errors, string fileName)

{

return Parse(new TextReaderTextStream(fileName, reader), errors);

}

public dynamic Parse(ITextStream stream, ErrorReporter errors)

{

var parser = CreateParser();

var result = parser.Parse(stream, errors);

return NormalizeResult(result);

}

public dynamic Parse(ITextStream stream, ErrorReporter errors, SourcePoint startLocation)

{

var parser = CreateParser();

var result = parser.Parse(stream, errors, startLocation);

return NormalizeResult(result);

}

System.Dataflow.Parser CreateParser()

{

var parser = factory.Create();

parser.GraphBuilder = new NodeGraphBuilder(store);

return parser;

}

dynamic NormalizeResult(object result)

{

if (result is Node)

return DynamicObjectNode.NodeToObject((Node)result);

return result;

}

}

}

Hanging out at PDC 09

Hey - I'm at PDC 09. Stop by the booth if you want to chat about all things "M".