using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Autodesk.AutoCAD.Runtime;
using System.Reflection;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.ApplicationServices;
namespace LispHelpers
{
/// <summary>
///
/// A crude implementation of contract enforcement for LISP
/// functions using Attributes.
///
/// The example below should be self-explainatory.
///
/// </summary>
public class RequiredArgumentsAttribute : System.Attribute
{
LispDataType[] argtypes = null;
public RequiredArgumentsAttribute( params LispDataType[] types )
{
this.argtypes = types
?? new LispDataType
[0]; }
public static TypedValue[] Check( ResultBuffer args, MethodBase method )
{
LispFunctionAttribute lfa = method.GetAttribute<LispFunctionAttribute>();
if( lfa == null )
throw new ArgumentException
( "Method must have the [LispFunction] Attribute applied to it" ); RequiredArgumentsAttribute rqa = method.GetAttribute<RequiredArgumentsAttribute>();
LispDataType[] prototype = rqa.argtypes;
string func = lfa.GlobalName;
TypedValue
[] values
= args
!= null ? args
.AsArray() : new TypedValue
[0]; if( prototype == null )
return values;
if( values.Length == 0 && prototype.Length == 0 )
return values;
if( values.Length != prototype.Length )
Error( "Incorrect number of arguments", func, prototype );
for( int i = 0; i < values.Length; i++ )
{
if( ! CheckArg( values[i], prototype[i] ) )
{
Error( string.Format( "Bad argument type (index = {0})", i ), func, prototype );
}
}
return values;
}
private static void Error( string msg, string func, LispDataType[] args )
{
string errorMsg = string.Format( "\n Error: {0}\n Usage: {1}\n\n", msg,
FormatArgs( func, args ) );
throw new ArgumentException
( errorMsg
); }
static bool CheckArg( TypedValue v, LispDataType type )
{
short code = (short) v.TypeCode;
object value = v.Value;
LispDataType argType = (LispDataType) code;
short typeCode = (short) type;
if( code == typeCode )
return true;
if( type.EqualsAny( LispDataType.Int16, LispDataType.Int32 ) &&
argType.EqualsAny( LispDataType.Int32, LispDataType.Int16 ) )
return true;
return false;
}
static string FormatArgs( string func, LispDataType[] args )
{
if( args == null || args.Length == 0 )
return "(no arguments)";
return string.Format( "({0} {1})", func,
string.Join( " ", args.Select( a => typeNames[a] ).ToArray() ) );
}
static Dictionary
<LispDataType,
string> typeNames
= ( (LispDataType
[]) Enum.GetValues( typeof( LispDataType
) ) ).ToDictionary( v => v, v => v.ToString() );
// currently not used
static Dictionary
<LispDataType, Type
> typeMap
= new Dictionary
<LispDataType, Type
>();
static Dictionary
<Type, LispDataType
> types
= new Dictionary
<Type, LispDataType
>();
static RequiredArgumentsAttribute()
{
// Not all types can be mapped to a LispDataType
types
[typeof( ObjectId
)] = LispDataType
.ObjectId; types
[typeof( SelectionSet
)] = LispDataType
.SelectionSet; types
[typeof( Int32
)] = LispDataType
.Int32; types
[typeof( Int16
)] = LispDataType
.Int16; types
[typeof( double )] = LispDataType
.Double; types
[typeof( string )] = LispDataType
.Text;
typeNames[LispDataType.ObjectId] = "ename";
typeNames[LispDataType.ListBegin] = "list";
typeNames[LispDataType.Angle] = "real";
typeNames[LispDataType.Int16] = "int";
typeNames[LispDataType.Int32] = "int";
typeNames[LispDataType.Text] = "string";
typeNames[LispDataType.T_atom] = "T/nil";
typeNames[LispDataType.DottedPair] = "alist";
typeNames[LispDataType.SelectionSet] = "Selection Set";
typeNames[LispDataType.Orientation] = "real";
typeNames[LispDataType.Double] = "real";
}
}
public static class ExtensionMethods
{
public static TypedValue[] ValidateRequiredArguments( this ResultBuffer args, MethodBase method )
{
return RequiredArgumentsAttribute.Check( args, method );
}
public static T GetAttribute<T>( this ICustomAttributeProvider provider ) where T : System.Attribute
{
T
[] a
= (T
[]) provider
.GetCustomAttributes( typeof( T
),
false ); if( a != null && a.Length > 0 )
return (T) a[0];
else
return null;
}
public static bool EqualsAny<T>( this T value, params T[] args )
{
return args.Contains( value );
}
}
public static class RequiredArgumentsAttributeExample
{
/// Example: This function requires an int, a real, and a string,
/// in that order. e.g., (foobar 99 2.5 "Hello") Note that range
/// checking is currently not performed on integers and Int32
/// and Int16 are treated the same (TODO).
[RequiredArguments( LispDataType.Int32, LispDataType.Double, LispDataType.Text )]
[LispFunction( "foobar" )]
public static object FooBar( ResultBuffer args )
{
// validate all arguments:
TypedValue[] arguments = args.ValidateRequiredArguments( MethodInfo.GetCurrentMethod() );
Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage( "(foobar): Arguments are correct\n" );
return null;
}
}
}