// 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;
}
}
}