// The CMLeonOS Project (https://github.com/Leonmmcoset/CMLeonOS)
// Copyright (C) 2025-present LeonOS 2 Developer Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
// #define DEBUG_DUMMY_TVALUE_MODIFY
using System;
using System.Collections.Generic;
namespace UniLua
{
using ULDebug = UniLua.Tools.ULDebug;
public class LuaTable {
public LuaTable MetaTable;
public uint NoTagMethodFlags;
public LuaTable(LuaState l) {
InitLuaTable(l);
}
~LuaTable()
{
Recycle();
}
public StkId Get(ref TValue key)
{
if(key.Tt == (int)LuaType.LUA_TNIL) { return TheNilValue; }
if(IsPositiveInteger(ref key))
{ return GetInt((int)key.NValue); }
if(key.Tt == (int)LuaType.LUA_TSTRING)
{ return GetStr(key.SValue()); }
var h = key.GetHashCode();
for(var node = GetHashNode(h); node != null; node = node.Next) {
if(node.Key.V == key) {
{ return node.Val; }
}
}
return TheNilValue;
}
public StkId GetInt(int key)
{
if(0 < key && key-1 < ArrayPart.Length)
{ return ArrayPart[key-1]; }
var k = new TValue();
k.SetNValue(key);
for(var node = GetHashNode(ref k); node != null; node = node.Next) {
if(node.Key.V.TtIsNumber() && node.Key.V.NValue == (double)key) {
return node.Val;
}
}
return TheNilValue;
}
public StkId GetStr(string key)
{
var h = key.GetHashCode();
for(var node = GetHashNode(h); node != null; node = node.Next) {
if(node.Key.V.TtIsString() && node.Key.V.SValue() == key)
{ return node.Val; }
}
return TheNilValue;
}
public void Set(ref TValue key, ref TValue val)
{
var cell = Get(ref key);
if(cell == TheNilValue) {
cell = NewTableKey(ref key);
}
cell.V.SetObj(ref val);
}
public void SetInt(int key, ref TValue val)
{
var cell = GetInt(key);
if(cell == TheNilValue) {
var k = new TValue();
k.SetNValue(key);
cell = NewTableKey(ref k);
}
cell.V.SetObj(ref val);
// Console.WriteLine(string.Format("---------------- SetInt {0} -> {1}", key, val));
// DumpParts();
}
/*
** returns the index of a `key' for table traversals. First goes all
** elements in the array part, then elements in the hash part. The
** beginning of a traversal is signaled by -1.
*/
private int FindIndex(StkId key)
{
if(key.V.TtIsNil())
{ return -1; }
// is `key' inside array part?
int i = ArrayIndex(ref key.V);
if(0 < i && i <= ArrayPart.Length)
{ return i-1; }
var n = GetHashNode(ref key.V);
// check whether `key' is somewhere in the chain
for(;;) {
if(L.V_RawEqualObj(ref n.Key.V, ref key.V))
{ return ArrayPart.Length + n.Index; }
n = n.Next;
// key not found
if(n == null) { L.G_RunError("invalid key to 'next'"); }
}
}
public bool Next(StkId key, StkId val)
{
// find original element
int i = FindIndex(key);
// try first array part
for(i++; i 0 && ArrayPart[j-1].V.TtIsNil()) {
/* there is a boundary in the array part: (binary) search for it */
uint i = 0;
while(j - i > 1) {
uint m = (i+j)/2;
if(ArrayPart[m-1].V.TtIsNil()) { j = m; }
else { i = m; }
}
return (int)i;
}
/* else must find a boundary in hash part */
else if(HashPart == DummyHashPart)
return (int)j;
else return UnboundSearch(j);
} }
public void Resize(int nasize, int nhsize)
{
int oasize = ArrayPart.Length;
var oldHashPart = HashPart;
if(nasize > oasize) // array part must grow?
SetArraryVector(nasize);
// create new hash part with appropriate size
SetNodeVector(nhsize);
// array part must shrink?
if(nasize < oasize) {
var oldArrayPart = ArrayPart;
ArrayPart = DummyArrayPart;
// re-insert elements from vanishing slice
for(int i=nasize; i 0 && (v.NValue % 1) == 0 && v.NValue <= int.MaxValue); //fix large number key bug
}
private HNode GetHashNode(int hashcode)
{
uint n = (uint)hashcode;
return HashPart[n % HashPart.Length];
}
private HNode GetHashNode(ref TValue v)
{
if(IsPositiveInteger(ref v)) { return GetHashNode((int)v.NValue); }
if(v.TtIsString()) { return GetHashNode(v.SValue().GetHashCode()); }
return GetHashNode(v.GetHashCode());
}
private void SetArraryVector(int size)
{
Utl.Assert(size >= ArrayPart.Length);
var newArrayPart = new StkId[size];
int i = 0;
for( ; i MAXBITS) { L.G_RunError("table overflow"); }
size = (1 << lsize);
HashPart = new HNode[size];
for(int i=0; i 0) {
var node = HashPart[--LastFree];
if(node.Key.V.TtIsNil()) { return node; }
}
return null;
}
/*
** returns the index for `key' if `key' is an appropriate key to live in
** the array part of the table, -1 otherwise.
*/
private int ArrayIndex(ref TValue k)
{
if(IsPositiveInteger(ref k))
return (int)k.NValue;
else
return -1;
}
private static readonly byte[] Log2_ = new byte[] {
0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
};
private int CeilLog2(int x)
{
Utl.Assert(x > 0);
int l = 0;
x--;
while(x >= 256) { l+=8; x>>=8; }
return l + Log2_[x];
}
private int CountInt(ref TValue key, ref int[] nums)
{
int k = ArrayIndex(ref key);
if(0 < k && k <= MAXASIZE) {
nums[CeilLog2(k)]++;
return 1;
}
else return 0;
}
private int NumUseArray(ref int[] nums)
{
int ause = 0;
int i = 1;
for(int lg=0, ttlg=1; lg<=MAXBITS; lg++, ttlg*=2) {
int lc = 0; // counter
int lim = ttlg;
if(lim > ArrayPart.Length) {
lim = ArrayPart.Length;
if(i > lim) { break; } // no more elements to count
}
// count elements in range (2^(lg-1), 2^lg]
for(; i<=lim; ++i) {
if(!ArrayPart[i-1].V.TtIsNil()) { lc++; }
}
nums[lg] += lc;
ause += lc;
}
return ause;
}
private int NumUseHash(ref int[] nums, ref int nasize)
{
int totaluse = 0;
int ause = 0;
int i = HashPart.Length;
while(i-- > 0) {
var n = HashPart[i];
if(!n.Val.V.TtIsNil()) {
ause += CountInt(ref n.Key.V, ref nums);
totaluse++;
}
}
nasize += ause;
return totaluse;
}
private int ComputeSizes(ref int[] nums, ref int nasize)
{
int a = 0;
int na = 0;
int n = 0;
for(int i=0, tti=1; tti/2 0) {
a += nums[i];
if(a > tti/2) {
n = tti;
na = a;
}
}
if(a == nasize) { break; } // all elements already conted
}
nasize = n;
Utl.Assert(nasize/2 <= na && na <= nasize);
return na;
}
private static int[] Nums = new int[MAXBITS + 1];
private void Rehash(ref TValue k)
{
for(int i=0; i<=MAXBITS; ++i) { Nums[i] = 0; }
int nasize = NumUseArray(ref Nums);
int totaluse = nasize;
totaluse += NumUseHash(ref Nums, ref nasize);
nasize += CountInt(ref k, ref Nums);
totaluse++;
int na = ComputeSizes(ref Nums, ref nasize);
Resize(nasize, totaluse-na);
}
private void DumpParts()
{
Console.WriteLine("------------------ [DumpParts] enter -----------------------");
Console.WriteLine("<< Array Part >>");
for(var i=0; i>");
for(var i=0; i LuaLimits.MAX_INT) {
/* table was built with bad purposes: resort to linear search */
i = 1;
while(!GetInt((int)i).V.TtIsNil()) { i++; }
return (int)(i-1);
}
}
/* now do a binary search between them */
while(j - i > 1) {
uint m = (i + j) / 2;
if(GetInt((int)m).V.TtIsNil()) { j = m; }
else { i = m; }
}
return (int)i;
}
}
}