11import 'dart:async' ;
22import 'dart:collection' ;
33import 'dart:io' ;
4+ import 'dart:math' ;
45
56import '/extensions.dart' ;
67import '/reyveld.dart' ;
@@ -22,6 +23,9 @@ typedef LuaResult = ({
2223
2324/// The main class for running lua scripts.
2425class Lua {
26+ static const String _classMetafield = "__classHash" ;
27+ static const String _objMetafield = "__objHash" ;
28+
2529 final WebSocketChannel ? socket;
2630
2731 final Set <Stopwatch > _stopwatches = {};
@@ -168,7 +172,33 @@ class Lua {
168172
169173 // Add all static exports as global object.
170174 for (final interface_ in interfaces) {
171- await addGlobal (state, interface_.className, interface_.staticTable);
175+ await addGlobal (state, interface_.className, {
176+ "className" : "_Type"
177+ }, metatable: {
178+ _classMetafield: getClassHash (interface_.className),
179+ "__index" : (Lua lua, LuaState state) async {
180+ final indexKey = await lua.getFromTop <String >(state);
181+ Reyveld .talker.verbose ("Index key: $indexKey " );
182+ if (await state.getMetafield (state.getTop (), _classMetafield) ==
183+ LuaType .luaString) {
184+ final hash = await lua.getFromTop <String >(state);
185+ final interface_ = interfaces
186+ .where ((i) => getClassHash (i.className) == hash)
187+ .singleOrNull;
188+ if (interface_ == null ) {
189+ throw Exception ("No interface found." );
190+ }
191+ final result = interface_.staticTable[indexKey];
192+ if (result == null ) {
193+ return Undefined ("No result found for $indexKey " );
194+ } else {
195+ return result;
196+ }
197+ } else {
198+ return Undefined ("No index key found." );
199+ }
200+ },
201+ });
172202 }
173203
174204 await addGlobal (state, "is" , (Lua lua, LuaState state) async {
@@ -185,21 +215,67 @@ class Lua {
185215 /// [value] is the table to add as the global.
186216 ///
187217 /// This will push the table to the stack and then set the global with the given name.
188- Future <void > addGlobal (LuaState state, String name, dynamic value) async {
189- // if (table == null) {
190- // return;
191- // }
218+ Future <void > addGlobal (LuaState state, String name, dynamic value,
219+ {Map <String , dynamic >? metatable}) async {
192220 await _pushToStack (state, value);
221+ if (metatable != null ) {
222+ await _applyMetatableToTop (state, metatable);
223+ }
193224 await state.setGlobal (name);
194225 }
195226
196- SInterface ? _getObject (LuaState state, String hash) => _objects[state]? [hash];
227+ SInterface ? getObject (LuaState state, String hash) => _objects[state]? [hash];
197228 void _setObject (LuaState state, String hash, SInterface interface_) =>
198229 _objects[state]! [hash] = interface_;
199230
231+ /// Applies a metatable to the top of the stack.
232+ ///
233+ /// This will create a new table on top of the previous table, and then add all key-value pairs from [metatable] to the table.
234+ /// Then, it will set the newly created table as the metatable of the top of the stack.
235+ Future <void > _applyMetatableToTop (
236+ LuaState state, Map <String , dynamic > metatable) async {
237+ state.newTable ();
238+ for (final key in metatable.keys) {
239+ await _pushToStack (state, key);
240+ await _pushToStack (state, metatable[key]);
241+ await state.setTable (state.getTop () - 2 );
242+ }
243+ state.setMetatable (state.getTop () - 1 );
244+ }
245+
246+ /// Handles undefined calls.
247+ Future <void > _undefined (Lua lua, LuaState state) async {
248+ while (state.getTop () > 3 ) {
249+ state.pop (1 );
250+ }
251+
252+ final col = await getFromTop <int >(state) ?? 0 ;
253+ final row = await getFromTop <int >(state) ?? 0 ;
254+ final undefined = await getFromTop (state);
255+ String message = "Undefined" ;
256+
257+ if (undefined is Map ) {
258+ if (undefined["message" ] != null ) {
259+ message = undefined["message" ];
260+ }
261+ }
262+
263+ throw ReyveldCallException (message,
264+ traceback: Traceback (getLoadedScript (state), row, col));
265+ }
266+
200267 /// Pushes a value to the stack.
201268 Future <void > _pushToStack (LuaState state, dynamic value) async {
202- if (value is String ) {
269+ Reyveld .talker.verbose ("Pushing $value to stack." );
270+ if (value is Undefined ) {
271+ // Reyveld.talker.verbose("Pushing undefined to stack.");
272+ await _pushToStack (state, {"message" : value.message});
273+ await _applyMetatableToTop (state, {
274+ "__call" : _undefined,
275+ "__index" : _undefined,
276+ "__newindex" : _undefined,
277+ });
278+ } else if (value is String ) {
203279 state.pushString (value);
204280 } else if (value is int ) {
205281 state.pushInteger (value);
@@ -219,11 +295,20 @@ class Lua {
219295 Reyveld .talker.verbose ("Wrapping $value in $interface_ ." );
220296 final hash = generateUUID ();
221297 _setObject (state, hash, interface_);
222- final table = interface_.toLua (hash );
298+ final table = interface_.toLua ();
223299 await _pushToStack (state, table);
300+ await _applyMetatableToTop (
301+ state, {_objMetafield: hash, "__index" : SInterface .betterIndex});
224302 } else if (value is FutureOr <dynamic > Function (Lua , LuaState )) {
303+ // Reyveld.talker.verbose("Pushing function to stack.");
225304 state.pushDartFunction ((state) async {
226- await _pushToStack (state, await value (this , state));
305+ try {
306+ await _pushToStack (state, await value (this , state));
307+ } catch (e, st) {
308+ state.pushString ("${e .toString ()}\n $st " );
309+ state.error ();
310+ return 0 ;
311+ }
227312 return 1 ;
228313 });
229314 } else if (value is LEntry ) {
@@ -246,6 +331,8 @@ class Lua {
246331 int row = finalArgs.removeAt (0 );
247332 int col = finalArgs.removeAt (0 );
248333
334+ final trackback = Traceback (getLoadedScript (state), row, col);
335+
249336 Map namedArgs = {};
250337
251338 if (value.hasNamedArgs &&
@@ -260,8 +347,9 @@ class Lua {
260347 final argValue = finalArgs[i];
261348 final argType = value.args.elementAt (i);
262349 if (argType.cast (argValue) == null && argType.required ) {
263- throw Exception (
264- "Type Mismatch for argument $argType at position $i ! Expected ${argType .type } but got ${argValue .runtimeType }" );
350+ throw ReyveldCallException (
351+ "Type Mismatch for argument $argType at position $i ! Expected ${argType .type } but got ${argValue .runtimeType }" ,
352+ traceback: trackback);
265353 } else {
266354 finalArgs[i] = argType.cast (argValue);
267355 }
@@ -272,8 +360,9 @@ class Lua {
272360 final argValue = namedArgs[key];
273361 final argType = value.namedArgs.where ((e) => e.name == key).single;
274362 if (argType.cast (argValue) == null && argType.required ) {
275- throw Exception (
276- "Type Mismatch for argument $argType at position $key ! Expected ${argType .type } but got ${argValue .runtimeType }" );
363+ throw ReyveldCallException (
364+ "Type Mismatch for argument $argType at position $key ! Expected ${argType .type } but got ${argValue .runtimeType }" ,
365+ traceback: trackback);
277366 } else {
278367 namedArgs[key] = argType.cast (argValue);
279368 }
@@ -345,14 +434,9 @@ class Lua {
345434 return 1 ;
346435 }
347436 } catch (e) {
348- _pushToStack (
349- state,
350- ReyveldCallException (
351- "Failed to call function '${value .name }' with $finalArgs $namedArgs " ,
352- traceback: Traceback (getLoadedScript (state), row, col))
353- .toString ());
354- state.error ();
355- return 0 ;
437+ throw ReyveldCallException (
438+ "Failed to call function '${value .name }' with $finalArgs $namedArgs " ,
439+ traceback: trackback);
356440 }
357441 });
358442 } else if (value is LField ) {
@@ -390,20 +474,36 @@ class Lua {
390474 await state.rawGetI (luaRegistryIndex, (result as LuaFuncRef ).ref);
391475 } else if (state.isTable (state.getTop ())) {
392476 /// If the top of the stack is a table, get the table and check if it has an objHash key.
393- final table = await _getTableFromState (state);
394- if (table.containsKey ("objHash" ) &&
395- _getObject (state, table["objHash" ]) != null ) {
396- /// If the table has an objHash key, then it means it is an interface for an object,
397- /// so get the object and return it.
398- result = _getObject (state, table["objHash" ])! .object;
399- } else if (table.containsKey ("__hash__" ) &&
400- interfaces.any ((key) => key.classHash == table["__hash__" ])) {
401- result = interfaces
402- .firstWhere ((key) => key.classHash == table["__hash__" ]);
403- } else if (table.keys.every ((key) => key is int )) {
404- result = table.values.toList ();
477+ final success = state.getMetatable (state.getTop ());
478+ Reyveld .talker.verbose ("Success: $success " );
479+ if (success) {
480+ final metatable = await getFromTop <Map >(state);
481+ final table = await _getTableFromState (state);
482+ Reyveld .talker.verbose ("Table: $table Metatable: $metatable " );
483+ if (metatable != null &&
484+ metatable.containsKey (_objMetafield) &&
485+ getObject (state, metatable[_objMetafield]) != null ) {
486+ result = getObject (state, metatable[_objMetafield])! .object;
487+ } else if (metatable != null &&
488+ metatable.containsKey (_classMetafield) &&
489+ interfaces
490+ .any ((key) => key.classHash == metatable[_classMetafield])) {
491+ result = interfaces
492+ .where ((key) => key.classHash == metatable[_classMetafield])
493+ .singleOrNull;
494+ } else if (table.keys.every ((key) => key is int )) {
495+ result = table.values.toList ();
496+ } else {
497+ result = table;
498+ }
405499 } else {
406- result = table;
500+ final table = await _getTableFromState (state);
501+ Reyveld .talker.verbose ("Table: $table " );
502+ if (table.keys.every ((key) => key is int )) {
503+ result = table.values.toList ();
504+ } else {
505+ result = table;
506+ }
407507 }
408508 } else if (state.isNoneOrNil (state.getTop ())) {
409509 result = null ;
@@ -429,11 +529,13 @@ class Lua {
429529 while (state.next (state.getTop () - 1 )) {
430530 dynamic value = await getFromTop (state);
431531 dynamic key = await getFromTop (state);
432- if (key is String && resultTable.isEmpty) {
433- resultTable = < String , dynamic > {};
434- } else if (key is int && resultTable.isEmpty) {
435- resultTable = < int , dynamic > {};
436- }
532+ // if (key is String && resultTable.isEmpty) {
533+ // Reyveld.talker.verbose("Keys are is string.");
534+ // resultTable = <String, dynamic>{};
535+ // } else if (key is int && resultTable.isEmpty) {
536+ // Reyveld.talker.verbose("Keys are is int.");
537+ // resultTable = <int, dynamic>{};
538+ // }
437539 resultTable[key] = value;
438540 await _pushToStack (state, key);
439541 }
@@ -662,7 +764,10 @@ ${enum_.key} = {
662764 return formattedEnums.join ("\n\n " );
663765 }
664766
665- static String getLoadedScript (LuaState state) => _loadedScripts[state] ?? "" ;
767+ static String getLoadedScript (LuaState state) {
768+ Reyveld .talker.verbose ("Getting script." );
769+ return _loadedScripts[state] ?? "" ;
770+ }
666771}
667772
668773/// A reference to a lua function.
@@ -730,8 +835,9 @@ class Traceback {
730835 final lines = code.split ("\n " );
731836 final buffer = StringBuffer ();
732837
733- final start = line - linesShown ~ / 2 ;
734- final end = line + linesShown ~ / 2 + (linesShown % 2 );
838+ final start = line - max <int >(linesShown ~ / 2 , 0 );
839+ final end =
840+ min <int >(line + linesShown ~ / 2 + (linesShown % 2 ), lines.length);
735841 for (int i = start; i < end; i++ ) {
736842 if (i < 0 || i >= lines.length) continue ;
737843 buffer.write (i == line ? "🔴" : " " );
@@ -740,3 +846,9 @@ class Traceback {
740846 return buffer.toString ();
741847 }
742848}
849+
850+ class Undefined {
851+ final String ? message;
852+
853+ const Undefined ([this .message]);
854+ }
0 commit comments