Exceptions.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. using Lua.CodeAnalysis;
  2. using Lua.CodeAnalysis.Syntax;
  3. using Lua.Internal;
  4. using Lua.Runtime;
  5. using System.Diagnostics;
  6. using System.Runtime.CompilerServices;
  7. namespace Lua;
  8. public class LuaParseException(string? chunkName, SourcePosition position, string message) : Exception(message)
  9. {
  10. public string? ChunkName { get; } = chunkName;
  11. public SourcePosition Position { get; } = position;
  12. public static void UnexpectedToken(string? chunkName, SourcePosition position, SyntaxToken token)
  13. {
  14. throw new LuaParseException(chunkName, position, $"unexpected symbol <{token.Type}> near '{token.Text}'");
  15. }
  16. public static void ExpectedToken(string? chunkName, SourcePosition position, SyntaxTokenType token)
  17. {
  18. throw new LuaParseException(chunkName, position, $"'{token}' expected");
  19. }
  20. public static void UnfinishedLongComment(string? chunkName, SourcePosition position)
  21. {
  22. throw new LuaParseException(chunkName, position, $"unfinished long comment (starting at line {position.Line})");
  23. }
  24. public static void SyntaxError(string? chunkName, SourcePosition position, SyntaxToken? token)
  25. {
  26. throw new LuaParseException(chunkName, position, $"syntax error {(token == null ? "" : $"near '{token.Value.Text}'")}");
  27. }
  28. public static void NoVisibleLabel(string label, string? chunkName, SourcePosition position)
  29. {
  30. throw new LuaParseException(chunkName, position, $"no visible label '{label}' for <goto>");
  31. }
  32. public static void BreakNotInsideALoop(string? chunkName, SourcePosition position)
  33. {
  34. throw new LuaParseException(chunkName, position, "<break> not inside a loop");
  35. }
  36. public override string Message => $"{ChunkName}:{Position.Line}: {base.Message}";
  37. }
  38. public class LuaCompileException(string chunkName, SourcePosition position, int offset, string message, string? nearToken) : Exception(GetMessageWithNearToken(message, nearToken))
  39. {
  40. public string ChunkName { get; } = chunkName;
  41. public int OffSet { get; } = offset;
  42. public SourcePosition Position => position;
  43. public string MainMessage => message;
  44. public string? NearToken => nearToken;
  45. public string MessageWithNearToken => base.Message;
  46. public override string Message => $"{ChunkName}:{Position.Line}: {base.Message}";
  47. static string GetMessageWithNearToken(string message, string? nearToken)
  48. {
  49. if (string.IsNullOrEmpty(nearToken))
  50. {
  51. return message;
  52. }
  53. return $"{message} near {nearToken}";
  54. }
  55. }
  56. public class LuaUnDumpException(string message) : Exception(message);
  57. internal interface ILuaTracebackBuildable
  58. {
  59. Traceback? BuildOrGet();
  60. }
  61. public class LuaRuntimeException : Exception, ILuaTracebackBuildable
  62. {
  63. public LuaRuntimeException(LuaThread? thread, Exception innerException) : base(innerException.Message, innerException)
  64. {
  65. Thread = thread;
  66. }
  67. public LuaRuntimeException(LuaThread? thread, LuaValue errorObject)
  68. {
  69. if (thread != null)
  70. {
  71. thread.CurrentException?.BuildOrGet();
  72. thread.ExceptionTrace.Clear();
  73. thread.CurrentException = this;
  74. }
  75. Thread = thread;
  76. ErrorObject = errorObject;
  77. }
  78. Traceback? luaTraceback;
  79. public Traceback? LuaTraceback
  80. {
  81. get
  82. {
  83. if (luaTraceback == null)
  84. {
  85. ((ILuaTracebackBuildable)this).BuildOrGet();
  86. }
  87. return luaTraceback;
  88. }
  89. }
  90. internal LuaThread? Thread { get; private set; } = default!;
  91. public LuaValue ErrorObject { get; }
  92. public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a, LuaValue b)
  93. {
  94. throw new LuaRuntimeException(thread, $"attempt to {op} a {a.TypeToString()} value with a {b.TypeToString()} value");
  95. }
  96. public static void AttemptInvalidOperation(LuaThread? thread, string op, LuaValue a)
  97. {
  98. throw new LuaRuntimeException(thread, $"attempt to {op} a {a.TypeToString()} value");
  99. }
  100. internal static void AttemptInvalidOperationOnLuaStack(LuaThread thread, string op, int lastPc, int reg)
  101. {
  102. var caller = thread.GetCurrentFrame();
  103. var luaValue = thread.Stack[caller.Base + reg];
  104. var function = caller.Function;
  105. var t = LuaDebug.GetName(((LuaClosure)function).Proto, lastPc, reg, out string? name);
  106. if (t == null || name == null)
  107. {
  108. throw new LuaRuntimeException(thread, $"attempt to {op} a {luaValue.TypeToString()} value");
  109. }
  110. else
  111. {
  112. throw new LuaRuntimeException(thread, $"attempt to {op} a {luaValue.TypeToString()} value ({t} '{name}')");
  113. }
  114. }
  115. public static void BadArgument(LuaThread? thread, int argumentId, string functionName)
  116. {
  117. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' (value expected)");
  118. }
  119. public static void BadArgument(LuaThread? thread, int argumentId, string functionName, LuaValueType[] expected)
  120. {
  121. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' ({string.Join(" or ", expected)} expected)");
  122. }
  123. public static void BadArgument(LuaThread? thread, int argumentId, string functionName, string expected, string actual)
  124. {
  125. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' ({expected} expected, got {actual})");
  126. }
  127. public static void BadArgument(LuaThread? thread, int argumentId, string functionName, string message)
  128. {
  129. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' ({message})");
  130. }
  131. public static void BadArgumentNumberIsNotInteger(LuaThread? thread, int argumentId, string functionName)
  132. {
  133. throw new LuaRuntimeException(thread, $"bad argument #{argumentId} to '{functionName}' (number has no integer representation)");
  134. }
  135. public static void ThrowBadArgumentIfNumberIsNotInteger(LuaThread? thread, string functionName, int argumentId, double value)
  136. {
  137. if (!MathEx.IsInteger(value))
  138. {
  139. BadArgumentNumberIsNotInteger(thread, argumentId, functionName);
  140. }
  141. }
  142. static string CreateMessage(Traceback traceback, LuaValue errorObject)
  143. {
  144. var pooledList = new PooledList<char>(64);
  145. pooledList.Clear();
  146. try
  147. {
  148. pooledList.AddRange("Lua-CSharp: ");
  149. traceback.WriteLastLuaTrace(ref pooledList);
  150. pooledList.AddRange(": ");
  151. pooledList.AddRange($"{errorObject}");
  152. return pooledList.AsSpan().ToString();
  153. }
  154. finally
  155. {
  156. pooledList.Dispose();
  157. }
  158. }
  159. [MethodImpl(MethodImplOptions.NoInlining)]
  160. Traceback? ILuaTracebackBuildable.BuildOrGet()
  161. {
  162. if (luaTraceback != null) return luaTraceback;
  163. if (Thread != null)
  164. {
  165. var callStack = Thread.ExceptionTrace.AsSpan();
  166. if (callStack.IsEmpty) return null;
  167. luaTraceback = new Traceback(Thread.State, callStack);
  168. Thread.ExceptionTrace.Clear();
  169. Thread = null;
  170. }
  171. return luaTraceback;
  172. }
  173. internal void Forget()
  174. {
  175. Thread?.ExceptionTrace.Clear();
  176. Thread = null;
  177. }
  178. public override string Message
  179. {
  180. get
  181. {
  182. if (InnerException != null) return InnerException.Message;
  183. if (LuaTraceback == null)
  184. {
  185. return ErrorObject.ToString();
  186. }
  187. return CreateMessage(LuaTraceback, ErrorObject);
  188. }
  189. }
  190. public override string ToString()
  191. {
  192. if (LuaTraceback == null)
  193. {
  194. return base.ToString();
  195. }
  196. var pooledList = new PooledList<char>(64);
  197. pooledList.Clear();
  198. try
  199. {
  200. pooledList.AddRange(Message);
  201. pooledList.Add('\n');
  202. pooledList.AddRange(LuaTraceback.ToString());
  203. pooledList.Add('\n');
  204. pooledList.AddRange(StackTrace);
  205. return pooledList.AsSpan().ToString();
  206. }
  207. finally
  208. {
  209. pooledList.Dispose();
  210. }
  211. }
  212. }
  213. public class LuaAssertionException(LuaThread? traceback, string message) : LuaRuntimeException(traceback, message);
  214. public class LuaModuleNotFoundException(string moduleName) : Exception($"module '{moduleName}' not found");
  215. public sealed class LuaCancelledException : OperationCanceledException, ILuaTracebackBuildable
  216. {
  217. Traceback? luaTraceback;
  218. public Traceback? LuaTraceback
  219. {
  220. get
  221. {
  222. if (luaTraceback == null)
  223. {
  224. ((ILuaTracebackBuildable)this).BuildOrGet();
  225. }
  226. return luaTraceback;
  227. }
  228. }
  229. internal LuaThread? Thread { get; private set; }
  230. internal LuaCancelledException(LuaThread thread, CancellationToken cancellationToken, Exception? innerException = null) : base("The operation was cancelled during execution on Lua.", innerException, cancellationToken)
  231. {
  232. thread.CurrentException?.BuildOrGet();
  233. thread.ExceptionTrace.Clear();
  234. thread.CurrentException = this;
  235. Thread = thread;
  236. }
  237. [MethodImpl(MethodImplOptions.NoInlining)]
  238. Traceback? ILuaTracebackBuildable.BuildOrGet()
  239. {
  240. if (luaTraceback != null) return luaTraceback;
  241. if (Thread != null)
  242. {
  243. var callStack = Thread.ExceptionTrace.AsSpan();
  244. if (callStack.IsEmpty) return null;
  245. luaTraceback = new Traceback(Thread.State, callStack);
  246. Thread.ExceptionTrace.Clear();
  247. Thread = null!;
  248. }
  249. return luaTraceback;
  250. }
  251. }