/SSharp.Core/Builtins/Macros/DotNetObject.cs
https://code.google.com/p/s-sharp/ · C# · 245 lines · 171 code · 28 blank · 46 comment · 38 complexity · b6c49fc1085a4d887360b20f27115f2a MD5 · raw file
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using SSharp.Core.Evaluator;
- using SSharp.Core.DataTypes;
- using System.Reflection;
- using SSharp.Core.Util;
- using System.Runtime.CompilerServices;
- using Microsoft.CSharp.RuntimeBinder;
-
- namespace SSharp.Core.Builtins.Macros {
- internal interface DotNetObjectWrapper {
- /// <summary>
- /// Gets the wrapped object
- /// </summary>
- Object UnderlyingObject { get; }
- }
-
- public class DotNetObject<T> : Macro, DotNetObjectWrapper {
- private static readonly Cache<Symbol, MemberInfo[]> members;
- private static readonly Cache<Symbol, Func<object, object>> getters;
- private static readonly Cache<Symbol, Func<object, object>> setters;
-
- static DotNetObject() {
- members = new Cache<Symbol, MemberInfo[]>(GetMembers);
- getters = new Cache<Symbol, Func<object, object>>(name => MemberAccess(name, false));
- setters = new Cache<Symbol, Func<object, object>>(name => MemberAccess(name, true));
- }
-
- /// <summary>
- /// The wrapped object
- /// </summary>
- private readonly T obj;
-
- public DotNetObject(T obj) {
- this.obj = obj;
- }
-
- public object Expand(IList<object> args) {
- return Expand(args, false);
- }
-
- /// <summary>
- /// Expands the macro, allowing for setter expansion.
- /// When called from the Set macro, isSetter should be true and a setter will be returned instead of a value.
- /// </summary>
- public object Expand(IList<object> args, bool isSetter) {
- if (args.Count != 1) {
- throw new SyntaxError(".Net object expects one argument, given " + args.Count);
- }
-
- Symbol name = args[0] as Symbol;
- if (name == null) {
- throw new SyntaxError(".Net object expects one argument, given " + args.Count);
- }
-
- if (isSetter) {
- return setters[name](obj);
- } else {
- // note that the getter is a method that is applied here
- return getters[name](obj);
- }
- }
-
- /// <summary>
- /// Returns a getter procedure for
- /// </summary>
- /// <param name="name"></param>
- /// <param name="isSetter"></param>
- /// <returns></returns>
- public static Func<object, object> MemberAccess(Symbol name, bool isSetter) {
- MemberInfo[] m = members[name];
- if (m.Length == 0) {
- throw new UnboundedVariableException("unkown member " + typeof(T).Name + "." + name);
- }
-
- if (m[0] is FieldInfo) {
- if (m.Length > 1) {
- throw new InvalidOperationException("More than one member with given name exists. " + typeof(T).Name + "." + name);
- }
- return ExpandField((FieldInfo)m[0], isSetter);
- }
-
- if (m[0] is PropertyInfo) {
- if (m.Length > 1) {
- throw new InvalidOperationException("More than one member with given name exists. " + typeof(T).Name + "." + name);
- }
- return ExpandProperty((PropertyInfo)m[0], isSetter);
- }
-
- if (m[0] is MethodInfo) {
- if (m.Any(member => !(member is MethodInfo))) {
- // one of the members is not a method
- throw new InvalidOperationException("More than one member with given name exists. " + typeof(T).Name + "." + name);
- }
-
- if (isSetter) {
- throw new InvalidOperationException("Cannot set the value of a method field!" + typeof(T).Name + "." + name);
- }
-
- return ExpandMethod(name.ToString());
- }
-
- throw new InvalidOperationException("Unknown member type. " + typeof(T).Name + "." + name);
- }
-
- /// <summary>
- /// Returns the Expand result for a FieldInfo
- /// </summary>
- private static Func<object, object> ExpandField(FieldInfo info, bool isSetter) {
- if (isSetter) {
- string methodName = "set_" + info.Name;
- return obj =>
- new PrimitiveProcedure(methodName,
- args => {
- if (args.Length != 1) {
- throw new ApplicationError(methodName + " expected one argument, given " + args.Length);
- }
- info.SetValue(obj, args[0]);
- return null;
- });
- } else {
- return obj => DotNetObject.WrapIfNeeded(info.GetValue(obj));
- }
- }
-
- /// <summary>
- /// Returns the Expand result for a PropertyInfo
- /// </summary>
- private static Func<object, object> ExpandProperty(PropertyInfo info, bool isSetter) {
- if (info.GetIndexParameters().Length == 0) {
- // normal property
- if (isSetter) {
- string methodName = "set_" + info.Name;
- return obj =>
- new PrimitiveProcedure(methodName,
- args => {
- if (args.Length != 1) {
- throw new ApplicationError(methodName + " expected one argument, given " + args.Length);
- }
- info.SetValue(obj, args[0], new object[0]);
- return null;
- });
- } else {
- return obj => DotNetObject.WrapIfNeeded(info.GetValue(obj, new object[0]));
- }
- } else {
- throw new NotImplementedException("Indexed properties are not implemented");
- }
- }
-
- /// <summary>
- /// Returns the Expand result for when the MemberInfo[] were all MethodInfos
- /// </summary>
- private static Func<object, object> ExpandMethod(string name) {
- return obj =>
- new PrimitiveProcedure(name,
- args =>
- DotNetObject.WrapIfNeeded(typeof(T).InvokeMember(name,
- BindingFlags.Instance
- | BindingFlags.Public
- | BindingFlags.InvokeMethod
- | BindingFlags.OptionalParamBinding,
- null,
- obj,
- args.Select(DotNetObject.Unwrap).ToArray())));
- }
-
- private static MemberInfo[] GetMembers(Symbol name) {
- return typeof(T).GetMember(name.ToString(),
- BindingFlags.Instance
- | BindingFlags.Public);
- }
-
- public override string ToString() {
- return obj.ToString();
- }
-
- public object UnderlyingObject {
- get { return obj; }
- }
- }
-
- /// <summary>
- /// Provides methods to wrap an object with a DotNetObject macro
- /// </summary>
- public static class DotNetObject {
- private static readonly Cache<Type, ConstructorInfo> dotNetObjectMacroConstructor;
-
- static DotNetObject() {
- dotNetObjectMacroConstructor = new Cache<Type, ConstructorInfo>(GetDotNetObjectMacroConstructor);
- }
-
- /// <summary>
- /// Returns the constructor for a DotNetObject typed over the given type
- /// </summary>
- private static ConstructorInfo GetDotNetObjectMacroConstructor(Type type) {
- return typeof(DotNetObject<>)
- .MakeGenericType(type)
- .GetConstructor(new[] { type });
- }
-
- /// <summary>
- /// If obj can be accessed by scheme directly, returns obj.
- /// If obj refers to a dotnet object that needs to be wrapped, returns new DotNetObject[typeof(obj)](obj)
- /// </summary>
- public static object WrapIfNeeded(object obj) {
- if (obj == null
- || obj is int
- || obj is string
- || obj is Cons
- || obj is Macro
- || obj is Procedure
- || obj is DotNetObjectWrapper) {
- // obj can be accessed directly by scheme; no need to wrap it
- return obj;
- } else {
- return Wrap(obj);
- }
- }
-
- /// <summary>
- /// Wraps the given object in a DotNetObject[typeof(obj)]
- /// </summary>
- public static object Wrap(object obj) {
- Type type = obj.GetType();
- ConstructorInfo constructor = dotNetObjectMacroConstructor[type];
- return constructor.Invoke(new[] { obj });
- }
-
- /// <summary>
- /// Unwraps an DotNetObject macro around the object, if there is one.
- /// </summary>
- public static object Unwrap(object obj) {
- DotNetObjectWrapper wrapper = obj as DotNetObjectWrapper;
- if (wrapper != null) {
- return wrapper.UnderlyingObject;
- } else {
- // obj isn't wrapped
- return obj;
- }
- }
- }
- }