diff --git a/lib/lua.dart b/lib/lua.dart index c83ac7b..60d5d46 100644 --- a/lib/lua.dart +++ b/lib/lua.dart @@ -1,8 +1,6 @@ export 'src/api/lua_state.dart'; export 'src/api/lua_basic_api.dart'; +export 'src/api/lua_state_accessor.dart'; export 'src/api/lua_aux_lib.dart'; export 'src/api/lua_type.dart'; export 'src/state/lua_userdata.dart'; - - - diff --git a/lib/src/api/lua_state_accessor.dart b/lib/src/api/lua_state_accessor.dart new file mode 100644 index 0000000..ad321ae --- /dev/null +++ b/lib/src/api/lua_state_accessor.dart @@ -0,0 +1,25 @@ + +import 'package:lua_dardo/src/api/lua_state.dart'; +import 'package:lua_dardo/src/api/lua_type.dart'; + +extension LuaStateAccessor on LuaState { + + String? getGlobalStringValue(String name) { + return (_globalToTop(name) == LuaType.luaString) ? toString2(getTop()) : null; + } + + double? getGlobalDoubleValue(String name) { + return (_globalToTop(name) == LuaType.luaNumber) ? toNumber(getTop()) : null; + } + + int? getGlobalIntValue(String name) { + return (_globalToTop(name) == LuaType.luaNumber) ? toInteger(getTop()) : null; + } + + bool? getGlobalBoolValue(String name) { + return (_globalToTop(name) == LuaType.luaBoolean) ? toBoolean(getTop()) : null; + } + + LuaType _globalToTop(String name) => getGlobal(name); + +} diff --git a/lib/src/state/lua_state_impl.dart b/lib/src/state/lua_state_impl.dart index 38be845..ef133be 100644 --- a/lib/src/state/lua_state_impl.dart +++ b/lib/src/state/lua_state_impl.dart @@ -34,10 +34,10 @@ class LuaStateImpl implements LuaState, LuaVM { LuaStack? _stack = LuaStack(); /// 注册表 - LuaTable? registry = LuaTable(0, 0); + LuaTable? registry = LuaTable(); LuaStateImpl() { - registry!.put(lua_ridx_globals, LuaTable(0, 0)); + registry!.put(lua_ridx_globals, LuaTable()); LuaStack stack = LuaStack(); stack.state = this; _pushLuaStack(stack); @@ -427,7 +427,7 @@ class LuaStateImpl implements LuaState, LuaVM { @override void createTable(int nArr, int nRec) { - _stack!.push(LuaTable(nArr, nRec)); + _stack!.push(LuaTable()); } @override diff --git a/lib/src/state/lua_table.dart b/lib/src/state/lua_table.dart index 28253ad..709a7aa 100644 --- a/lib/src/state/lua_table.dart +++ b/lib/src/state/lua_table.dart @@ -1,169 +1,69 @@ -import 'dart:collection'; - -import '../number/lua_number.dart'; +import 'package:lua_dardo/src/state/table/lua_table_array.dart'; +import 'package:lua_dardo/src/state/table/lua_table_keys.dart'; +import 'package:lua_dardo/src/state/table/lua_table_map.dart'; class LuaTable { /// 元表 LuaTable? metatable; - List? arr; - Map? map; - - // used by next() - Map? keys; - Object? lastKey; - late bool changed; - - LuaTable(int nArr, int nRec) { - if (nArr > 0) { - // arr = List(nArr); - arr = []; - } - if (nRec > 0) { - // map = Map(nRec); - map = HashMap(); - } - } - - bool hasMetafield(String fieldName) { - return metatable != null && metatable!.get(fieldName) != null; - } - int length() { - return arr == null ? 0 : arr!.length; - } - - Object? get(Object? key) { - key = floatToInteger(key); - - if (arr != null && key is int) { - int idx = key; - if (idx >= 1 && idx <= arr!.length) { - return arr![idx - 1]; - } - } + final LuaTableArray arr = LuaTableArray(); + final LuaTableMap map = LuaTableMap(); + final LuaTableKeys keys = LuaTableKeys(); - return map != null ? map![key] : null; - } + bool hasMetafield(String fieldName) => metatable?.get(fieldName) != null; void put(Object? key, Object? val) { if (key == null) { throw Exception("table index is nil!"); } + if (key is double && key.isNaN) { throw Exception("table index is NaN!"); } - key = floatToInteger(key); - if (key is int) { - int idx = key; - if (idx >= 1) { - if (arr == null) { - arr = []; - } - - int arrLen = arr!.length; - if (idx <= arrLen) { - arr![idx-1] = val; - if (idx == arrLen && val == null) { - shrinkArray(); - } - return; - } - if (idx == arrLen + 1) { - if (map != null) { - map!.remove(key); - } - if (val != null) { - arr!.add(val); - expandArray(); - } - return; - } - } - } - - if (val != null) { - if (map == null) { - map = HashMap(); - } - map![key] = val; + if (val == null) { + _remove(key); + } else if (arr.isArrayIndex(key)) { + arr[key] = val; + } else if (arr.isArrayIndex(key, forInsert: true)) { + arr[key] = val; + _expandArray(); } else { - if (map != null) { - map!.remove(key); - } + map[key] = val; } + + keys.update(arr.length, map.keys); } - Object? floatToInteger(Object? key) { - if (key is double) { - double f = key; - if (LuaNumber.isInteger(f)) { - return f.toInt(); - } + void _remove(Object key) { + if (arr.isArrayIndex(key)) { + arr.removeAt(key); + } else { + map.remove(key); } - return key; } - void shrinkArray() { - for (int i = arr!.length - 1; i >= 0; i--) { - if (arr![i] == null) { - arr!.removeAt(i); - } + void _expandArray() { + int key = arr.length + 1; + Object? value = map[key]; + while (value != null) { + arr[key] = value; + map.remove(key); + + key++; + value = map[key]; } } - void expandArray() { - if (map != null) { - for (int idx = arr!.length + 1; ; idx++) { - Object? val = map!.remove(idx); - if (val != null) { - arr!.add(val); - } else { - break; - } - } + Object? get(Object? key) { + if (key != null) { + return (arr.isArrayIndex(key)) ? arr[key] : map[key]; } + return null; } - Object? nextKey(Object? key) { - if (keys == null || (key == null && changed)) { - initKeys(); - changed = false; - } + Object? nextKey(Object? key) => keys.nextKey(key); - Object? nextKey = keys![key]; - if (nextKey == null && key != null && key != lastKey) { - throw Exception("invalid key to 'next'"); - } + int length() => arr.length; - return nextKey; - } - - void initKeys() { - if (keys == null) { - keys = HashMap(); - } else { - keys!.clear(); - } - Object? key = null; - if (arr != null) { - for (int i = 0; i < arr!.length; i++) { - if (arr![i] != null) { - int nextKey = i + 1; - keys![key] = nextKey; - key = nextKey; - } - } - } - if (map != null) { - for (Object? k in map!.keys) { - Object? v = map![k]; - if (v != null) { - keys![key] = k; - key = k; - } - } - } - lastKey = key; - } -} \ No newline at end of file +} diff --git a/lib/src/state/table/lua_table_array.dart b/lib/src/state/table/lua_table_array.dart new file mode 100644 index 0000000..5248f85 --- /dev/null +++ b/lib/src/state/table/lua_table_array.dart @@ -0,0 +1,49 @@ + +import 'package:lua_dardo/src/number/lua_number.dart'; + +class LuaTableArray { + + List? _arr; + + bool isArrayIndex(Object key, {bool forInsert = false}) { + int idx = _floatToIndex(key); + bool canInsert = forInsert && idx == length; + return idx >= 0 && (idx < length || canInsert); + } + + void operator []= (Object key, Object value) { + int idx = _floatToIndex(key); + if (idx < length) { + _initializedArray[idx] = value; + } else { + _initializedArray.add(value); + } + } + + Object operator [] (Object key) { + int idx = _floatToIndex(key); + return _initializedArray[idx]; + } + + void removeAt(Object key) { + int idx = _floatToIndex(key); + _initializedArray.removeAt(idx); + } + + int _floatToIndex(Object key) { + int intKey = (key is double && LuaNumber.isInteger(key)) + ? key.toInt() + : (key is int) ? key : -1; + return intKey -1; + } + + int get length => (_arr != null) ? _initializedArray.length : 0; + + List get _initializedArray { + if (_arr == null) { + _arr = []; + } + return _arr!; + } + +} diff --git a/lib/src/state/table/lua_table_keys.dart b/lib/src/state/table/lua_table_keys.dart new file mode 100644 index 0000000..9ad2b55 --- /dev/null +++ b/lib/src/state/table/lua_table_keys.dart @@ -0,0 +1,51 @@ + +class LuaTableKeys { + + final Map _nextKeys = {}; + + Object? lastKey; + + int _arrLength = 0; + Iterable _keys = []; + bool _changed = false; + + void update(int arrLength, Iterable keys) { + _arrLength = arrLength; + _keys = keys; + _changed = true; + } + + Object? nextKey(Object? key) { + if (_changed) { + regenerate(); + } + + Object? nextKey = _nextKeys[key]; + + if (nextKey == null && key != null && key != lastKey) { + throw Exception("invalid key to 'next'"); + } + + return nextKey; + } + + void regenerate() { + _nextKeys.clear(); + + Object? key = null; + + for (int i=1; i < _arrLength; i++) { + _nextKeys[key] = i; + key = i+1; + } + + for (Object nextKey in _keys) { + _nextKeys[key] = nextKey; + key = nextKey; + } + + lastKey = key; + _changed = false; + } + +} diff --git a/lib/src/state/table/lua_table_map.dart b/lib/src/state/table/lua_table_map.dart new file mode 100644 index 0000000..20aa11e --- /dev/null +++ b/lib/src/state/table/lua_table_map.dart @@ -0,0 +1,26 @@ + +class LuaTableMap { + + Map? _map; + + void operator []= (Object key, Object value) { + _initializedMap[key] = value; + } + + Object? operator [] (Object key) => _initializedMap[key]; + + void remove(Object key) => _initializedMap.remove(key); + + Iterable get keys => (_map != null) + ? _initializedMap.keys + : Iterable.empty(); + + Map get _initializedMap { + if (_map == null) { + _map = {}; + } + + return _map!; + } + +} diff --git a/test/stdlib/closure_test.dart b/test/stdlib/closure_test.dart new file mode 100644 index 0000000..569c96b --- /dev/null +++ b/test/stdlib/closure_test.dart @@ -0,0 +1,50 @@ + +import 'package:lua_dardo/lua.dart'; +import 'package:test/test.dart'; + +import '../utils/test_utils.dart'; + +bool testTypeChanging() { + return TestUtils.performLuaCode(''' +function newCounter () + local i = 0 + return function () -- anonymous function + i = i + 1 + return i + end +end + +c1 = newCounter() +print(c1()) --> 1 +print(c1()) --> 2 +clResult = c1(); +''', + (state) { + return state.getGlobalIntValue("clResult") == 3; + } + ); +} + +bool testClosureOverride() { + return TestUtils.performLuaCode(''' +do + local oldSin = math.sin + local k = math.pi/180 + math.sin = function (x) + return oldSin(x*k) + end + sinRes = math.sin(90); +end +''', + (state) { + double? result = state.getGlobalDoubleValue("sinRes"); + return result != null && (result - 1.0).abs() < 0.001; + } + ); +} + +void main() { + // https://www.lua.org/pil/6.1.html + test('lua Closures test', () => expect(testTypeChanging(), true)); + test('lua Closure override test', () => expect(testClosureOverride(), true)); +} diff --git a/test/stdlib/dart_test.dart b/test/stdlib/dart_test.dart new file mode 100644 index 0000000..3da8969 --- /dev/null +++ b/test/stdlib/dart_test.dart @@ -0,0 +1,28 @@ + +import 'package:lua_dardo/lua.dart'; +import 'package:test/test.dart'; + +bool testDartFunctions() { + try { + bool changed = false; + LuaState state = LuaState.newState(); + state.openLibs(); + state.pushDartFunction((ls) { + changed = true; + return 0; + }); + state.setGlobal('test'); + state.loadString('test()'); + state.pCall(0, 0, 1); + + return changed; + } catch(e,s){ + print('$e\n$s'); + } + + return false; +} + +void main() { + test('lua Dart function calling test', () => expect(testDartFunctions(), true)); +} diff --git a/test/stdlib/table_test.dart b/test/stdlib/table_test.dart index 7c6432f..704a986 100644 --- a/test/stdlib/table_test.dart +++ b/test/stdlib/table_test.dart @@ -25,15 +25,55 @@ table.move(a1 ,1,#a1,4) print(table.concat(a1, ",")) '''); state.pCall(0, 0, 1); - }catch(e,s){ + } catch(e,s){ print('$e\n$s'); return false; } return true; } +bool testTableTraversing(){ + bool result = false; + + try{ + LuaState state = LuaState.newState(); + state.openLibs(); + state.loadString(r''' +local alphabet = {} +alphabet.a = 'alpha' +alphabet.b = 'beta' + +--trigger keys initializastion +local val = next(alphabet, 'b') + +alphabet.c = 'gamma' + +result = false; +for k, v in pairs(alphabet) do + print(k, v) + if (k == 'c' and v == 'gamma') + then + result = true; + end +end +'''); + state.pCall(0, 0, 1); + LuaType resultType = state.getGlobal("result"); + if (resultType == LuaType.luaBoolean) { + result = state.toBoolean(state.getTop()); + } + } catch(e,s){ + print('$e\n$s'); + } + + return result; +} + void main() { test('lua table standard library test', () { expect(testTable(), true); }); + test('\nlua traversing table test', () { + expect(testTableTraversing(), true); + }); } diff --git a/test/stdlib/types_test.dart b/test/stdlib/types_test.dart new file mode 100644 index 0000000..56ac3ff --- /dev/null +++ b/test/stdlib/types_test.dart @@ -0,0 +1,48 @@ + +import 'package:lua_dardo/lua.dart'; +import 'package:test/test.dart'; + +import '../utils/test_utils.dart'; + +bool testTypeInference(String value, String type) { + return TestUtils.performLuaCode(''' +varType = type($value); +''', + (state) => (state.getGlobalStringValue("varType") == type)); +} + +bool testTypeChanging() { + return TestUtils.performLuaCode(''' +nilType = type(a) --> (`a' is not initialized) + +a = 10 +numberType = type(a) + +a = "a string!!" +stringType = type(a) + +a = print +functionType = type(a) +''', + (state) { + return state.getGlobalStringValue("nilType") == "nil" + && state.getGlobalStringValue("numberType") == "number" + && state.getGlobalStringValue("stringType") == "string" + && state.getGlobalStringValue("functionType") == "function"; + } + ); + + +} + +void main() { + /// https://www.lua.org/pil/2.html + test('lua String type inference test', () => expect(testTypeInference('\"Hello World \"', "string"), true)); + test('lua Number type inference test', () => expect(testTypeInference('10.4*3', "number"), true)); + test('lua Function type test', () => expect(testTypeInference('print', "function"), true)); + test('lua type() type inference test', () => expect(testTypeInference('type', "function"), true)); + test('lua boolean type inference test', () => expect(testTypeInference('true', "boolean"), true)); + test('lua nil type inference test', () => expect(testTypeInference('nil', "nil"), true)); + test('lua type() result type inference test', () => expect(testTypeInference('type(X)', "string"), true)); + test('lua variable type changing test', () => expect(testTypeChanging(), true)); +} diff --git a/test/utils/test_utils.dart b/test/utils/test_utils.dart new file mode 100644 index 0000000..9a9700e --- /dev/null +++ b/test/utils/test_utils.dart @@ -0,0 +1,24 @@ + +import 'package:lua_dardo/lua.dart'; + +class TestUtils { + + TestUtils._internal(); + + static bool performLuaCode(String luaCode, TestEvaluator evaluator) { + try{ + LuaState state = LuaState.newState(); + state.openLibs(); + state.loadString(luaCode); + state.pCall(0, 0, 1); + + return evaluator(state); + } catch(e,s){ + print('$e\n$s'); + } + return false; + } + +} + +typedef bool TestEvaluator(LuaState state);