LuaValue.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. using System.Globalization;
  2. using System.Runtime.CompilerServices;
  3. using System.Runtime.InteropServices;
  4. using Lua.Internal;
  5. using Lua.Runtime;
  6. namespace Lua;
  7. public enum LuaValueType : byte
  8. {
  9. Nil,
  10. Boolean,
  11. String,
  12. Number,
  13. Function,
  14. Thread,
  15. LightUserData,
  16. UserData,
  17. Table,
  18. }
  19. [StructLayout(LayoutKind.Auto)]
  20. public readonly struct LuaValue : IEquatable<LuaValue>
  21. {
  22. public static readonly LuaValue Nil = default;
  23. public readonly LuaValueType Type;
  24. readonly double value;
  25. readonly object? referenceValue;
  26. public bool TryRead<T>(out T result)
  27. {
  28. var t = typeof(T);
  29. switch (Type)
  30. {
  31. case LuaValueType.Number:
  32. if (t == typeof(float))
  33. {
  34. var v = (float)value;
  35. result = Unsafe.As<float, T>(ref v);
  36. return true;
  37. }
  38. else if (t == typeof(double))
  39. {
  40. var v = value;
  41. result = Unsafe.As<double, T>(ref v);
  42. return true;
  43. }
  44. else if (t == typeof(int))
  45. {
  46. if (!MathEx.IsInteger(value)) break;
  47. var v = (int)value;
  48. result = Unsafe.As<int, T>(ref v);
  49. return true;
  50. }
  51. else if (t == typeof(long))
  52. {
  53. if (!MathEx.IsInteger(value)) break;
  54. var v = (long)value;
  55. result = Unsafe.As<long, T>(ref v);
  56. return true;
  57. }
  58. else if (t == typeof(object))
  59. {
  60. result = (T)(object)value;
  61. return true;
  62. }
  63. else
  64. {
  65. break;
  66. }
  67. case LuaValueType.Boolean:
  68. if (t == typeof(bool))
  69. {
  70. var v = value != 0;
  71. result = Unsafe.As<bool, T>(ref v);
  72. return true;
  73. }
  74. else if (t == typeof(object))
  75. {
  76. result = (T)(object)value;
  77. return true;
  78. }
  79. else
  80. {
  81. break;
  82. }
  83. case LuaValueType.String:
  84. if (t == typeof(string))
  85. {
  86. var v = referenceValue!;
  87. result = Unsafe.As<object, T>(ref v);
  88. return true;
  89. }
  90. else if (t == typeof(double))
  91. {
  92. result = default!;
  93. return TryParseToDouble(out Unsafe.As<T, double>(ref result));
  94. }
  95. else if (t == typeof(object))
  96. {
  97. result = (T)referenceValue!;
  98. return true;
  99. }
  100. else
  101. {
  102. break;
  103. }
  104. case LuaValueType.Function:
  105. if (t == typeof(LuaFunction) || t.IsSubclassOf(typeof(LuaFunction)))
  106. {
  107. var v = referenceValue!;
  108. result = Unsafe.As<object, T>(ref v);
  109. return true;
  110. }
  111. else if (t == typeof(object))
  112. {
  113. result = (T)referenceValue!;
  114. return true;
  115. }
  116. else
  117. {
  118. break;
  119. }
  120. case LuaValueType.Thread:
  121. if (t == typeof(LuaThread))
  122. {
  123. var v = referenceValue!;
  124. result = Unsafe.As<object, T>(ref v);
  125. return true;
  126. }
  127. else if (t == typeof(object))
  128. {
  129. result = (T)referenceValue!;
  130. return true;
  131. }
  132. else
  133. {
  134. break;
  135. }
  136. case LuaValueType.LightUserData:
  137. {
  138. if (referenceValue is T tValue)
  139. {
  140. result = tValue;
  141. return true;
  142. }
  143. break;
  144. }
  145. case LuaValueType.UserData:
  146. if (t == typeof(ILuaUserData) || typeof(ILuaUserData).IsAssignableFrom(t))
  147. {
  148. if (referenceValue is T tValue)
  149. {
  150. result = tValue;
  151. return true;
  152. }
  153. break;
  154. }
  155. else if (t == typeof(object))
  156. {
  157. result = (T)referenceValue!;
  158. return true;
  159. }
  160. else
  161. {
  162. break;
  163. }
  164. case LuaValueType.Table:
  165. if (t == typeof(LuaTable))
  166. {
  167. var v = referenceValue!;
  168. result = Unsafe.As<object, T>(ref v);
  169. return true;
  170. }
  171. else if (t == typeof(object))
  172. {
  173. result = (T)referenceValue!;
  174. return true;
  175. }
  176. else
  177. {
  178. break;
  179. }
  180. }
  181. result = default!;
  182. return false;
  183. }
  184. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  185. internal bool TryReadBool(out bool result)
  186. {
  187. if (Type == LuaValueType.Boolean)
  188. {
  189. result = value != 0;
  190. return true;
  191. }
  192. result = default!;
  193. return false;
  194. }
  195. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  196. internal bool TryReadNumber(out double result)
  197. {
  198. if (Type == LuaValueType.Number)
  199. {
  200. result = value;
  201. return true;
  202. }
  203. result = default!;
  204. return false;
  205. }
  206. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  207. internal bool TryReadTable(out LuaTable result)
  208. {
  209. if (Type == LuaValueType.Table)
  210. {
  211. var v = referenceValue!;
  212. result = Unsafe.As<object, LuaTable>(ref v);
  213. return true;
  214. }
  215. result = default!;
  216. return false;
  217. }
  218. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  219. internal bool TryReadFunction(out LuaFunction result)
  220. {
  221. if (Type == LuaValueType.Function)
  222. {
  223. var v = referenceValue!;
  224. result = Unsafe.As<object, LuaFunction>(ref v);
  225. return true;
  226. }
  227. result = default!;
  228. return false;
  229. }
  230. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  231. internal bool TryReadString(out string result)
  232. {
  233. if (Type == LuaValueType.String)
  234. {
  235. var v = referenceValue!;
  236. result = Unsafe.As<object, string>(ref v);
  237. return true;
  238. }
  239. result = default!;
  240. return false;
  241. }
  242. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  243. internal bool TryReadDouble(out double result)
  244. {
  245. if (Type == LuaValueType.Number)
  246. {
  247. result = value;
  248. return true;
  249. }
  250. return TryParseToDouble(out result);
  251. }
  252. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  253. internal static bool TryReadOrSetDouble(ref LuaValue luaValue, out double result)
  254. {
  255. if (luaValue.Type == LuaValueType.Number)
  256. {
  257. result = luaValue.value;
  258. return true;
  259. }
  260. if (luaValue.TryParseToDouble(out result))
  261. {
  262. luaValue = result;
  263. return true;
  264. }
  265. return false;
  266. }
  267. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  268. internal double UnsafeReadDouble()
  269. {
  270. return value;
  271. }
  272. bool TryParseToDouble(out double result)
  273. {
  274. if (Type != LuaValueType.String)
  275. {
  276. result = default!;
  277. return false;
  278. }
  279. var str = Unsafe.As<string>(referenceValue!);
  280. var span = str.AsSpan().Trim();
  281. if (span.Length == 0)
  282. {
  283. result = default!;
  284. return false;
  285. }
  286. var sign = 1;
  287. var first = span[0];
  288. if (first is '+')
  289. {
  290. sign = 1;
  291. span = span[1..];
  292. }
  293. else if (first is '-')
  294. {
  295. sign = -1;
  296. span = span[1..];
  297. }
  298. if (span.Length > 2 && span[0] is '0' && span[1] is 'x' or 'X')
  299. {
  300. // TODO: optimize
  301. try
  302. {
  303. var d = HexConverter.ToDouble(span) * sign;
  304. result = d;
  305. return true;
  306. }
  307. catch (FormatException)
  308. {
  309. result = default!;
  310. return false;
  311. }
  312. }
  313. else
  314. {
  315. return double.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture, out result);
  316. }
  317. }
  318. public T Read<T>()
  319. {
  320. if (!TryRead<T>(out var result)) throw new InvalidOperationException($"Cannot convert LuaValueType.{Type} to {typeof(T).FullName}.");
  321. return result;
  322. }
  323. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  324. internal T UnsafeRead<T>()
  325. {
  326. switch (Type)
  327. {
  328. case LuaValueType.Boolean:
  329. {
  330. var v = value != 0;
  331. return Unsafe.As<bool, T>(ref v);
  332. }
  333. case LuaValueType.Number:
  334. {
  335. var v = value;
  336. return Unsafe.As<double, T>(ref v);
  337. }
  338. case LuaValueType.String:
  339. case LuaValueType.Thread:
  340. case LuaValueType.Function:
  341. case LuaValueType.Table:
  342. case LuaValueType.LightUserData:
  343. case LuaValueType.UserData:
  344. {
  345. var v = referenceValue!;
  346. return Unsafe.As<object, T>(ref v);
  347. }
  348. }
  349. return default!;
  350. }
  351. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  352. public bool ToBoolean()
  353. {
  354. if (Type == LuaValueType.Boolean) return value != 0;
  355. if (Type is LuaValueType.Nil) return false;
  356. return true;
  357. }
  358. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  359. public LuaValue(object obj)
  360. {
  361. Type = LuaValueType.LightUserData;
  362. referenceValue = obj;
  363. }
  364. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  365. public LuaValue(bool value)
  366. {
  367. Type = LuaValueType.Boolean;
  368. this.value = value ? 1 : 0;
  369. }
  370. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  371. public LuaValue(double value)
  372. {
  373. Type = LuaValueType.Number;
  374. this.value = value;
  375. }
  376. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  377. public LuaValue(string value)
  378. {
  379. Type = LuaValueType.String;
  380. referenceValue = value;
  381. }
  382. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  383. public LuaValue(LuaFunction value)
  384. {
  385. Type = LuaValueType.Function;
  386. referenceValue = value;
  387. }
  388. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  389. public LuaValue(LuaTable value)
  390. {
  391. Type = LuaValueType.Table;
  392. referenceValue = value;
  393. }
  394. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  395. public LuaValue(LuaThread value)
  396. {
  397. Type = LuaValueType.Thread;
  398. referenceValue = value;
  399. }
  400. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  401. public LuaValue(ILuaUserData value)
  402. {
  403. Type = LuaValueType.UserData;
  404. referenceValue = value;
  405. }
  406. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  407. public static implicit operator LuaValue(bool value)
  408. {
  409. return new(value);
  410. }
  411. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  412. public static implicit operator LuaValue(double value)
  413. {
  414. return new(value);
  415. }
  416. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  417. public static implicit operator LuaValue(string value)
  418. {
  419. return new(value);
  420. }
  421. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  422. public static implicit operator LuaValue(LuaTable value)
  423. {
  424. return new(value);
  425. }
  426. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  427. public static implicit operator LuaValue(LuaFunction value)
  428. {
  429. return new(value);
  430. }
  431. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  432. public static implicit operator LuaValue(LuaThread value)
  433. {
  434. return new(value);
  435. }
  436. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  437. public override int GetHashCode()
  438. {
  439. return Type switch
  440. {
  441. LuaValueType.Nil => 0,
  442. LuaValueType.Boolean or LuaValueType.Number => value.GetHashCode(),
  443. LuaValueType.String => Unsafe.As<string>(referenceValue)!.GetHashCode(),
  444. _ => referenceValue!.GetHashCode()
  445. };
  446. }
  447. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  448. public bool Equals(LuaValue other)
  449. {
  450. if (other.Type != Type) return false;
  451. return Type switch
  452. {
  453. LuaValueType.Nil => true,
  454. LuaValueType.Boolean or LuaValueType.Number => other.value == value,
  455. LuaValueType.String => Unsafe.As<string>(other.referenceValue) == Unsafe.As<string>(referenceValue),
  456. _ => other.referenceValue!.Equals(referenceValue)
  457. };
  458. }
  459. public override bool Equals(object? obj)
  460. {
  461. return obj is LuaValue value1 && Equals(value1);
  462. }
  463. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  464. public static bool operator ==(LuaValue a, LuaValue b)
  465. {
  466. return a.Equals(b);
  467. }
  468. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  469. public static bool operator !=(LuaValue a, LuaValue b)
  470. {
  471. return !a.Equals(b);
  472. }
  473. public override string ToString()
  474. {
  475. return Type switch
  476. {
  477. LuaValueType.Nil => "nil",
  478. LuaValueType.Boolean => Read<bool>() ? "true" : "false",
  479. LuaValueType.String => Read<string>(),
  480. LuaValueType.Number => Read<double>().ToString(CultureInfo.InvariantCulture),
  481. LuaValueType.Function => $"function: {referenceValue!.GetHashCode()}",
  482. LuaValueType.Thread => $"thread: {referenceValue!.GetHashCode()}",
  483. LuaValueType.Table => $"table: {referenceValue!.GetHashCode()}",
  484. LuaValueType.LightUserData => $"userdata: {referenceValue!.GetHashCode()}",
  485. LuaValueType.UserData => $"userdata: {referenceValue!.GetHashCode()}",
  486. _ => "",
  487. };
  488. }
  489. public static bool TryGetLuaValueType(Type type, out LuaValueType result)
  490. {
  491. if (type == typeof(double) || type == typeof(float) || type == typeof(int) || type == typeof(long))
  492. {
  493. result = LuaValueType.Number;
  494. return true;
  495. }
  496. else if (type == typeof(bool))
  497. {
  498. result = LuaValueType.Boolean;
  499. return true;
  500. }
  501. else if (type == typeof(string))
  502. {
  503. result = LuaValueType.String;
  504. return true;
  505. }
  506. else if (type == typeof(LuaFunction) || type.IsSubclassOf(typeof(LuaFunction)))
  507. {
  508. result = LuaValueType.Function;
  509. return true;
  510. }
  511. else if (type == typeof(LuaTable))
  512. {
  513. result = LuaValueType.Table;
  514. return true;
  515. }
  516. else if (type == typeof(LuaThread))
  517. {
  518. result = LuaValueType.Thread;
  519. return true;
  520. }
  521. else if (type == typeof(ILuaUserData) || type.IsAssignableFrom(typeof(ILuaUserData)))
  522. {
  523. result = LuaValueType.UserData;
  524. return true;
  525. }
  526. result = default;
  527. return false;
  528. }
  529. internal ValueTask<int> CallToStringAsync(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  530. {
  531. if (this.TryGetMetamethod(context.State, Metamethods.ToString, out var metamethod))
  532. {
  533. if (!metamethod.TryReadFunction(out var func))
  534. {
  535. LuaRuntimeException.AttemptInvalidOperation(context.State.GetTraceback(), "call", metamethod);
  536. }
  537. context.State.Push(this);
  538. return func.InvokeAsync(context with
  539. {
  540. ArgumentCount = 1,
  541. FrameBase = context.Thread.Stack.Count - 1,
  542. }, buffer, cancellationToken);
  543. }
  544. else
  545. {
  546. buffer.Span[0] = ToString();
  547. return new(1);
  548. }
  549. }
  550. }