Commit 75f46254 authored by capresti's avatar capresti
Browse files

Merged contributions from Bastian Eicher:

- Preserved stack trace on .NET exceptions
- Added support for attribute based registration of methods
- Global object tracking for auto completion providers
Merged fix for Issue #5

git-svn-id: http://luainterface.googlecode.com/svn/trunk@13 63eb109e-e254-0410-a61e-ed0b8f8614f5
parent b4943c8d
...@@ -4,6 +4,7 @@ namespace LuaInterface ...@@ -4,6 +4,7 @@ namespace LuaInterface
using System; using System;
using System.IO; using System.IO;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
...@@ -20,7 +21,8 @@ namespace LuaInterface ...@@ -20,7 +21,8 @@ namespace LuaInterface
* - removed all Open*Lib() functions * - removed all Open*Lib() functions
* - all libs automatically open in the Lua class constructor (just assign nil to unwanted libs) * - all libs automatically open in the Lua class constructor (just assign nil to unwanted libs)
* */ * */
public class Lua : IDisposable [CLSCompliant(true)]
public class Lua : IDisposable
{ {
static string init_luanet = static string init_luanet =
...@@ -192,19 +194,19 @@ namespace LuaInterface ...@@ -192,19 +194,19 @@ namespace LuaInterface
object err = translator.getObject(luaState, -1); object err = translator.getObject(luaState, -1);
LuaDLL.lua_settop(luaState, oldTop); LuaDLL.lua_settop(luaState, oldTop);
// If the 'error' on the stack is an actual C# exception, just rethrow it. Otherwise the value must have started Exception ex = err as Exception;
// as a true Lua error and is best interpreted as a string - wrap it in a LuaException and rethrow.
Exception thrown = err as Exception;
if (thrown == null) // A true Lua error, best interpreted as a string - wrap it in a LuaException and rethrow.
if (ex == null)
{ {
if (err == null) if (err == null)
err = "Unknown Lua Error"; err = "Unknown Lua Error";
thrown = new LuaException(err.ToString()); throw new LuaException(err.ToString());
} }
throw thrown; // If the 'error' on the stack is an actual C# exception, wrap and rethrow it to preserve the stack trace
throw new LuaException(".NET exception occured", ex);
} }
...@@ -230,7 +232,7 @@ namespace LuaInterface ...@@ -230,7 +232,7 @@ namespace LuaInterface
} }
/// <summary> /// <summary>
/// CP: Submitted by Paul Moore ///
/// </summary> /// </summary>
/// <param name="chunk"></param> /// <param name="chunk"></param>
/// <param name="name"></param> /// <param name="name"></param>
...@@ -240,11 +242,15 @@ namespace LuaInterface ...@@ -240,11 +242,15 @@ namespace LuaInterface
int oldTop = LuaDLL.lua_gettop(luaState); int oldTop = LuaDLL.lua_gettop(luaState);
if (LuaDLL.luaL_loadbuffer(luaState, chunk, name) != 0) if (LuaDLL.luaL_loadbuffer(luaState, chunk, name) != 0)
ThrowExceptionFromError(oldTop); ThrowExceptionFromError(oldTop);
return translator.getFunction(luaState, -1);
LuaFunction result = translator.getFunction(luaState, -1);
translator.popValues(luaState, oldTop);
return result;
} }
/// <summary> /// <summary>
/// CP: Submitted by Paul Moore ///
/// </summary> /// </summary>
/// <param name="fileName"></param> /// <param name="fileName"></param>
/// <returns></returns> /// <returns></returns>
...@@ -254,7 +260,10 @@ namespace LuaInterface ...@@ -254,7 +260,10 @@ namespace LuaInterface
if (LuaDLL.luaL_loadfile(luaState, fileName) != 0) if (LuaDLL.luaL_loadfile(luaState, fileName) != 0)
ThrowExceptionFromError(oldTop); ThrowExceptionFromError(oldTop);
return translator.getFunction(luaState, -1); LuaFunction result = translator.getFunction(luaState, -1);
translator.popValues(luaState, oldTop);
return result;
} }
...@@ -360,8 +369,124 @@ namespace LuaInterface ...@@ -360,8 +369,124 @@ namespace LuaInterface
setObject(remainingPath,value); setObject(remainingPath,value);
} }
LuaDLL.lua_settop(luaState,oldTop); LuaDLL.lua_settop(luaState,oldTop);
}
// Globals auto-complete
if (value == null)
{
// Remove now obsolete entries
globals.Remove(fullPath);
}
else
{
// Add new entries
if (!globals.Contains(fullPath))
registerGlobal(fullPath, value.GetType(), 0);
}
}
} }
#region Globals auto-complete
private readonly List<string> globals = new List<string>();
private bool globalsSorted;
/// <summary>
/// An alphabetically sorted list of all globals (objects, methods, etc.) externally added to this Lua instance
/// </summary>
/// <remarks>Members of globals are also listed. The formatting is optimized for text input auto-completion.</remarks>
public IEnumerable<string> Globals
{
get
{
// Only sort list when necessary
if (!globalsSorted)
{
globals.Sort();
globalsSorted = true;
}
return globals;
}
}
/// <summary>
/// Adds an entry to <see cref="globals"/> (recursivley handles 2 levels of members)
/// </summary>
/// <param name="path">The index accessor path ot the entry</param>
/// <param name="type">The type of the entry</param>
/// <param name="recursionCounter">How deep have we gone with recursion?</param>
private void registerGlobal(string path, Type type, int recursionCounter)
{
// If the type is a global method, list it directly
if (type == typeof(LuaCSFunction))
{
// Format for easy method invocation
globals.Add(path + "(");
}
// If the type is a class or an interface and recursion hasn't been running too long, list the members
else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < 2)
{
#region Methods
foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(method.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) &&
(method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) &&
// Exclude some generic .NET methods that wouldn't be very usefull in Lua
method.Name != "GetType" && method.Name != "GetHashCode" && method.Name != "Equals" &&
method.Name != "ToString" && method.Name != "Clone" && method.Name != "Dispose" &&
method.Name != "GetEnumerator" && method.Name != "CopyTo" &&
!method.Name.StartsWith("get_", StringComparison.Ordinal) &&
!method.Name.StartsWith("set_", StringComparison.Ordinal) &&
!method.Name.StartsWith("add_", StringComparison.Ordinal) &&
!method.Name.StartsWith("remove_", StringComparison.Ordinal))
{
// Format for easy method invocation
string command = path + ":" + method.Name + "(";
if (method.GetParameters().Length == 0) command += ")";
globals.Add(command);
}
}
#endregion
#region Fields
foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(field.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) &&
(field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0))
{
// Go into recursion for members
registerGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1);
}
}
#endregion
#region Properties
foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (
// Check that the LuaHideAttribute and LuaGlobalAttribute were not applied
(property.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) &&
(property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0)
// Exclude some generic .NET properties that wouldn't be very usefull in Lua
&& property.Name != "Item")
{
// Go into recursion for members
registerGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1);
}
}
#endregion
}
// Otherwise simply add the element to the list
else globals.Add(path);
// List will need to be sorted on next access
globalsSorted = false;
}
#endregion
/* /*
* Navigates a table in the top of the stack, returning * Navigates a table in the top of the stack, returning
* the value of the specified field * the value of the specified field
......
using System; using System;
using System.Collections.Generic; using System.Runtime.Serialization;
using System.Text;
namespace LuaInterface namespace LuaInterface
{ {
/// <summary> /// <summary>
/// Add a specific type for Lua exceptions (kevinh) /// Exceptions thrown by the Lua runtime
/// </summary> /// </summary>
public class LuaException : ApplicationException [Serializable]
public class LuaException : Exception
{ {
public LuaException(string reason) public LuaException()
: base(reason) {}
{
} public LuaException(string message) : base(message)
{}
public LuaException(string message, Exception innerException) : base(message, innerException)
{}
protected LuaException(SerializationInfo info, StreamingContext context) : base(info, context)
{}
} }
} }
\ No newline at end of file
using System;
namespace LuaInterface
{
/// <summary>
/// Marks a method for global usage in Lua scripts
/// </summary>
/// <see cref="LuaRegistrationHelper.TaggedInstanceMethods"/>
/// <see cref="LuaRegistrationHelper.TaggedStaticMethods"/>
[AttributeUsage(AttributeTargets.Method)]
public sealed class LuaGlobalAttribute : Attribute
{
/// <summary>
/// An alternative name to use for calling the function in Lua - leave empty for CLR name
/// </summary>
public string Name { get; set; }
/// <summary>
/// A description of the function
/// </summary>
public string Description { get; set; }
}
}
using System;
namespace LuaInterface
{
/// <summary>
/// Marks a method, field or property to be hidden from Lua auto-completion
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)]
public sealed class LuaHideAttribute : Attribute
{}
}
...@@ -10,8 +10,7 @@ ...@@ -10,8 +10,7 @@
<RootNamespace>LuaInterface</RootNamespace> <RootNamespace>LuaInterface</RootNamespace>
<AssemblyName>LuaInterface</AssemblyName> <AssemblyName>LuaInterface</AssemblyName>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>luainterface.snk</AssemblyOriginatorKeyFile>
</AssemblyOriginatorKeyFile>
<SccProjectName> <SccProjectName>
</SccProjectName> </SccProjectName>
<SccLocalPath> <SccLocalPath>
...@@ -59,6 +58,9 @@ ...@@ -59,6 +58,9 @@
<Compile Include="LuaBase.cs" /> <Compile Include="LuaBase.cs" />
<Compile Include="LuaException.cs" /> <Compile Include="LuaException.cs" />
<Compile Include="LuaFunction.cs" /> <Compile Include="LuaFunction.cs" />
<Compile Include="LuaGlobalAttribute.cs" />
<Compile Include="LuaHideAttribute.cs" />
<Compile Include="LuaRegistrationHelper.cs" />
<Compile Include="LuaTable.cs" /> <Compile Include="LuaTable.cs" />
<Compile Include="LuaUserData.cs" /> <Compile Include="LuaUserData.cs" />
<Compile Include="Metatables.cs" /> <Compile Include="Metatables.cs" />
......
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace LuaInterface
{
public static class LuaRegistrationHelper
{
#region Tagged instance methods
/// <summary>
/// Registers all public instance methods in an object tagged with <see cref="LuaGlobalAttribute"/> as Lua global functions
/// </summary>
/// <param name="lua">The Lua VM to add the methods to</param>
/// <param name="o">The object to get the methods from</param>
public static void TaggedInstanceMethods(Lua lua, object o)
{
#region Sanity checks
if (lua == null) throw new ArgumentNullException("lua");
if (o == null) throw new ArgumentNullException("o");
#endregion
foreach (MethodInfo method in o.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), true))
{
if (string.IsNullOrEmpty(attribute.Name))
lua.RegisterFunction(method.Name, o, method); // CLR name
else
lua.RegisterFunction(attribute.Name, o, method); // Custom name
}
}
}
#endregion
#region Tagged static methods
/// <summary>
/// Registers all public static methods in a class tagged with <see cref="LuaGlobalAttribute"/> as Lua global functions
/// </summary>
/// <param name="lua">The Lua VM to add the methods to</param>
/// <param name="type">The class type to get the methods from</param>
public static void TaggedStaticMethods(Lua lua, Type type)
{
#region Sanity checks
if (lua == null) throw new ArgumentNullException("lua");
if (type == null) throw new ArgumentNullException("type");
if (!type.IsClass) throw new ArgumentException("The type must be a class!", "type");
#endregion
foreach (MethodInfo method in type.GetMethods(BindingFlags.Static | BindingFlags.Public))
{
foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), false))
{
if (string.IsNullOrEmpty(attribute.Name))
lua.RegisterFunction(method.Name, null, method); // CLR name
else
lua.RegisterFunction(attribute.Name, null, method); // Custom name
}
}
}
#endregion
#region Enumeration
/// <summary>
/// Registers an enumeration's values for usage as a Lua variable table
/// </summary>
/// <typeparam name="T">The enum type to register</typeparam>
/// <param name="lua">The Lua VM to add the enum to</param>
[SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The type parameter is used to select an enum type")]
public static void Enumeration<T>(Lua lua)
{
#region Sanity checks
if (lua == null) throw new ArgumentNullException("lua");
#endregion
Type type = typeof(T);
if (!type.IsEnum) throw new ArgumentException("The type must be an enumeration!");
string[] names = Enum.GetNames(type);
var values = (T[])Enum.GetValues(type);
lua.NewTable(type.Name);
for (int i = 0; i < names.Length; i++)
{
string path = type.Name + "." + names[i];
lua[path] = values[i];
}
}
#endregion
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment