You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

219 lines
5.9 KiB

import { hasOwnProperty, LuaType, tostring } from './utils'
type MetaMethods =
// unary op
| '__unm'
| '__bnot'
| '__len'
// binary op
| '__add'
| '__sub'
| '__mul'
| '__mod'
| '__pow'
| '__div'
| '__idiv'
| '__band'
| '__bor'
| '__bxor'
| '__shl'
| '__shr'
| '__concat'
| '__eq'
| '__lt'
| '__le'
// other
| '__index'
| '__newindex'
| '__call'
| '__pairs'
| '__ipairs'
| '__tostring'
class Table {
public numValues: LuaType[] = [undefined]
public strValues: Record<string, LuaType> = {}
public keys: string[] = []
public values: LuaType[] = []
public metatable: Table | null = null
public constructor(initialiser?: Record<string, LuaType> | LuaType[] | ((t: Table) => void)) {
if (initialiser === undefined) return
if (typeof initialiser === 'function') {
initialiser(this)
return
}
if (Array.isArray(initialiser)) {
this.insert(...initialiser)
return
}
for (const key in initialiser) {
if (hasOwnProperty(initialiser, key)) {
let value = initialiser[key]
if (value === null) value = undefined
this.set(key, value)
}
}
}
public get(key: LuaType): LuaType {
const value = this.rawget(key)
if (value === undefined && this.metatable) {
const mm = this.metatable.get('__index') as Table | Function
if (mm instanceof Table) {
return mm.get(key)
}
if (typeof mm === 'function') {
const v = mm.call(undefined, this, key)
return v instanceof Array ? v[0] : v
}
}
return value
}
public rawget(key: LuaType): LuaType {
switch (typeof key) {
case 'string':
if (hasOwnProperty(this.strValues, key)) {
return this.strValues[key]
}
break
case 'number':
if (key > 0 && key % 1 === 0) {
return this.numValues[key]
}
}
const index = this.keys.indexOf(tostring(key))
return index === -1 ? undefined : this.values[index]
}
public getMetaMethod(name: MetaMethods): Function {
return this.metatable && (this.metatable.rawget(name) as Function)
}
public set(key: LuaType, value: LuaType): LuaType {
const mm = this.metatable && this.metatable.get('__newindex')
if (mm) {
const oldValue = this.rawget(key)
if (oldValue === undefined) {
if (mm instanceof Table) {
return mm.set(key, value)
}
if (typeof mm === 'function') {
return mm(this, key, value)
}
}
}
this.rawset(key, value)
}
public setFn(key: string): (v: LuaType) => void {
return v => this.set(key, v)
}
public rawset(key: LuaType, value: LuaType): void {
switch (typeof key) {
case 'string':
this.strValues[key] = value
return
case 'number':
if (key > 0 && key % 1 === 0) {
this.numValues[key] = value
return
}
}
const K = tostring(key)
const index = this.keys.indexOf(K)
if (index > -1) {
this.values[index] = value
return
}
this.values[this.keys.length] = value
this.keys.push(K)
}
public insert(...values: LuaType[]): void {
this.numValues.push(...values)
}
public toObject(): unknown[] | Record<string, unknown> {
const outputAsArray = Object.keys(this.strValues).length === 0 && this.getn() > 0
const result: unknown[] | Record<string, unknown> = outputAsArray ? [] : {}
for (let i = 1; i < this.numValues.length; i++) {
const propValue = this.numValues[i]
const value = propValue instanceof Table ? propValue.toObject() : propValue
if (outputAsArray) {
const res = result as unknown[]
res[i - 1] = value
} else {
const res = result as Record<string, unknown>
res[String(i - 1)] = value
}
}
for (const key in this.strValues) {
if (hasOwnProperty(this.strValues, key)) {
const propValue = this.strValues[key]
const value = propValue instanceof Table ? propValue.toObject() : propValue
const res = result as Record<string, unknown>
res[key] = value
}
}
return result
}
public getn(): number {
const vals = this.numValues
const keys: boolean[] = []
for (const i in vals) {
if (hasOwnProperty(vals, i)) {
keys[i] = true
}
}
let j = 0
while (keys[j + 1]) {
j += 1
}
// Following translated from ltable.c (http://www.lua.org/source/5.3/ltable.c.html)
if (j > 0 && vals[j] === undefined) {
/* there is a boundary in the array part: (binary) search for it */
let i = 0
while (j - i > 1) {
const m = Math.floor((i + j) / 2)
if (vals[m] === undefined) {
j = m
} else {
i = m
}
}
return i
}
return j
}
}
export { MetaMethods, Table }