PageRenderTime 72ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using SSharp.Core.Evaluator;
  6. using SSharp.Core.DataTypes;
  7. using System.Reflection;
  8. using SSharp.Core.Util;
  9. using System.Runtime.CompilerServices;
  10. using Microsoft.CSharp.RuntimeBinder;
  11. namespace SSharp.Core.Builtins.Macros {
  12. internal interface DotNetObjectWrapper {
  13. /// <summary>
  14. /// Gets the wrapped object
  15. /// </summary>
  16. Object UnderlyingObject { get; }
  17. }
  18. public class DotNetObject<T> : Macro, DotNetObjectWrapper {
  19. private static readonly Cache<Symbol, MemberInfo[]> members;
  20. private static readonly Cache<Symbol, Func<object, object>> getters;
  21. private static readonly Cache<Symbol, Func<object, object>> setters;
  22. static DotNetObject() {
  23. members = new Cache<Symbol, MemberInfo[]>(GetMembers);
  24. getters = new Cache<Symbol, Func<object, object>>(name => MemberAccess(name, false));
  25. setters = new Cache<Symbol, Func<object, object>>(name => MemberAccess(name, true));
  26. }
  27. /// <summary>
  28. /// The wrapped object
  29. /// </summary>
  30. private readonly T obj;
  31. public DotNetObject(T obj) {
  32. this.obj = obj;
  33. }
  34. public object Expand(IList<object> args) {
  35. return Expand(args, false);
  36. }
  37. /// <summary>
  38. /// Expands the macro, allowing for setter expansion.
  39. /// When called from the Set macro, isSetter should be true and a setter will be returned instead of a value.
  40. /// </summary>
  41. public object Expand(IList<object> args, bool isSetter) {
  42. if (args.Count != 1) {
  43. throw new SyntaxError(".Net object expects one argument, given " + args.Count);
  44. }
  45. Symbol name = args[0] as Symbol;
  46. if (name == null) {
  47. throw new SyntaxError(".Net object expects one argument, given " + args.Count);
  48. }
  49. if (isSetter) {
  50. return setters[name](obj);
  51. } else {
  52. // note that the getter is a method that is applied here
  53. return getters[name](obj);
  54. }
  55. }
  56. /// <summary>
  57. /// Returns a getter procedure for
  58. /// </summary>
  59. /// <param name="name"></param>
  60. /// <param name="isSetter"></param>
  61. /// <returns></returns>
  62. public static Func<object, object> MemberAccess(Symbol name, bool isSetter) {
  63. MemberInfo[] m = members[name];
  64. if (m.Length == 0) {
  65. throw new UnboundedVariableException("unkown member " + typeof(T).Name + "." + name);
  66. }
  67. if (m[0] is FieldInfo) {
  68. if (m.Length > 1) {
  69. throw new InvalidOperationException("More than one member with given name exists. " + typeof(T).Name + "." + name);
  70. }
  71. return ExpandField((FieldInfo)m[0], isSetter);
  72. }
  73. if (m[0] is PropertyInfo) {
  74. if (m.Length > 1) {
  75. throw new InvalidOperationException("More than one member with given name exists. " + typeof(T).Name + "." + name);
  76. }
  77. return ExpandProperty((PropertyInfo)m[0], isSetter);
  78. }
  79. if (m[0] is MethodInfo) {
  80. if (m.Any(member => !(member is MethodInfo))) {
  81. // one of the members is not a method
  82. throw new InvalidOperationException("More than one member with given name exists. " + typeof(T).Name + "." + name);
  83. }
  84. if (isSetter) {
  85. throw new InvalidOperationException("Cannot set the value of a method field!" + typeof(T).Name + "." + name);
  86. }
  87. return ExpandMethod(name.ToString());
  88. }
  89. throw new InvalidOperationException("Unknown member type. " + typeof(T).Name + "." + name);
  90. }
  91. /// <summary>
  92. /// Returns the Expand result for a FieldInfo
  93. /// </summary>
  94. private static Func<object, object> ExpandField(FieldInfo info, bool isSetter) {
  95. if (isSetter) {
  96. string methodName = "set_" + info.Name;
  97. return obj =>
  98. new PrimitiveProcedure(methodName,
  99. args => {
  100. if (args.Length != 1) {
  101. throw new ApplicationError(methodName + " expected one argument, given " + args.Length);
  102. }
  103. info.SetValue(obj, args[0]);
  104. return null;
  105. });
  106. } else {
  107. return obj => DotNetObject.WrapIfNeeded(info.GetValue(obj));
  108. }
  109. }
  110. /// <summary>
  111. /// Returns the Expand result for a PropertyInfo
  112. /// </summary>
  113. private static Func<object, object> ExpandProperty(PropertyInfo info, bool isSetter) {
  114. if (info.GetIndexParameters().Length == 0) {
  115. // normal property
  116. if (isSetter) {
  117. string methodName = "set_" + info.Name;
  118. return obj =>
  119. new PrimitiveProcedure(methodName,
  120. args => {
  121. if (args.Length != 1) {
  122. throw new ApplicationError(methodName + " expected one argument, given " + args.Length);
  123. }
  124. info.SetValue(obj, args[0], new object[0]);
  125. return null;
  126. });
  127. } else {
  128. return obj => DotNetObject.WrapIfNeeded(info.GetValue(obj, new object[0]));
  129. }
  130. } else {
  131. throw new NotImplementedException("Indexed properties are not implemented");
  132. }
  133. }
  134. /// <summary>
  135. /// Returns the Expand result for when the MemberInfo[] were all MethodInfos
  136. /// </summary>
  137. private static Func<object, object> ExpandMethod(string name) {
  138. return obj =>
  139. new PrimitiveProcedure(name,
  140. args =>
  141. DotNetObject.WrapIfNeeded(typeof(T).InvokeMember(name,
  142. BindingFlags.Instance
  143. | BindingFlags.Public
  144. | BindingFlags.InvokeMethod
  145. | BindingFlags.OptionalParamBinding,
  146. null,
  147. obj,
  148. args.Select(DotNetObject.Unwrap).ToArray())));
  149. }
  150. private static MemberInfo[] GetMembers(Symbol name) {
  151. return typeof(T).GetMember(name.ToString(),
  152. BindingFlags.Instance
  153. | BindingFlags.Public);
  154. }
  155. public override string ToString() {
  156. return obj.ToString();
  157. }
  158. public object UnderlyingObject {
  159. get { return obj; }
  160. }
  161. }
  162. /// <summary>
  163. /// Provides methods to wrap an object with a DotNetObject macro
  164. /// </summary>
  165. public static class DotNetObject {
  166. private static readonly Cache<Type, ConstructorInfo> dotNetObjectMacroConstructor;
  167. static DotNetObject() {
  168. dotNetObjectMacroConstructor = new Cache<Type, ConstructorInfo>(GetDotNetObjectMacroConstructor);
  169. }
  170. /// <summary>
  171. /// Returns the constructor for a DotNetObject typed over the given type
  172. /// </summary>
  173. private static ConstructorInfo GetDotNetObjectMacroConstructor(Type type) {
  174. return typeof(DotNetObject<>)
  175. .MakeGenericType(type)
  176. .GetConstructor(new[] { type });
  177. }
  178. /// <summary>
  179. /// If obj can be accessed by scheme directly, returns obj.
  180. /// If obj refers to a dotnet object that needs to be wrapped, returns new DotNetObject[typeof(obj)](obj)
  181. /// </summary>
  182. public static object WrapIfNeeded(object obj) {
  183. if (obj == null
  184. || obj is int
  185. || obj is string
  186. || obj is Cons
  187. || obj is Macro
  188. || obj is Procedure
  189. || obj is DotNetObjectWrapper) {
  190. // obj can be accessed directly by scheme; no need to wrap it
  191. return obj;
  192. } else {
  193. return Wrap(obj);
  194. }
  195. }
  196. /// <summary>
  197. /// Wraps the given object in a DotNetObject[typeof(obj)]
  198. /// </summary>
  199. public static object Wrap(object obj) {
  200. Type type = obj.GetType();
  201. ConstructorInfo constructor = dotNetObjectMacroConstructor[type];
  202. return constructor.Invoke(new[] { obj });
  203. }
  204. /// <summary>
  205. /// Unwraps an DotNetObject macro around the object, if there is one.
  206. /// </summary>
  207. public static object Unwrap(object obj) {
  208. DotNetObjectWrapper wrapper = obj as DotNetObjectWrapper;
  209. if (wrapper != null) {
  210. return wrapper.UnderlyingObject;
  211. } else {
  212. // obj isn't wrapped
  213. return obj;
  214. }
  215. }
  216. }
  217. }