import fengari from 'fengari' ; import { luaopen_base } from 'fengari/src/lbaselib' ; import { luaopen_math } from 'fengari/src/lmathlib' ; import { luaopen_string } from 'fengari/src/lstrlib' ; import { luaopen_table } from 'fengari/src/ltablib' ; import { luaopen_coroutine } from 'fengari/src/lcorolib' ; import { readFileSync } from 'fs' ; import { join } from 'path' ; const lua = fengari . lua ; const lauxlib = fengari . lauxlib ; const luaBuidinCode = readFileSync ( join ( __dirname , 'helpers.lua' ) ) . toString ( ) ; export class LuaVm { private L : any ; private ctx : Map < string , any > ; constructor ( ) { this . L = lauxlib . luaL_newstate ( ) ; const map = new Map < string , any > ( ) ; map . set ( '_G' , luaopen_base ) . set ( 'string' , luaopen_string ) . set ( 'table' , luaopen_table ) . set ( 'math' , luaopen_math ) . set ( 'coroutine' , luaopen_coroutine ) ; map . forEach ( ( lib , name ) => { lauxlib . luaL_requiref ( this . L , fengari . to_luastring ( name ) , lib , 1 ) ; lua . lua_pop ( this . L , 1 ) ; } ) ; this . ctx = new Map < string , any > ( ) ; } public run ( source ) { source = luaBuidinCode + source ; for ( const [ key , value ] of this . ctx ) { const n = this . pushStackValue ( this . L , value ) ; if ( n !== 0 ) { lua . lua_setglobal ( this . L , key ) ; } } lauxlib . luaL_dostring ( this . L , fengari . to_luastring ( source ) ) ; return this . getStackValue ( this . L , - 1 ) ; } public set ( key , value , target ? ) { if ( typeof value === 'function' ) { value = this . wrapFunc ( key , value , target ) ; } this . ctx . set ( key , value ) ; return this ; } private wrapFunc ( key , func , target ? ) { const wrapped = ( L ) : any => { const getArgs = ( ) : any [ ] => { const nArgs = lua . lua_gettop ( L ) ; const args = [ ] ; for ( let i = 0 ; i < nArgs ; i ++ ) { const value = this . getStackValue ( L , i + 1 ) ; args . push ( value ) ; } return args ; } ; const args = getArgs ( ) ; const res = func . call ( target , ... args ) ; if ( res instanceof Promise && lua . lua_isyieldable ( L ) ) { Promise . resolve ( res ) . then ( r => { if ( r === undefined ) { lua . lua_resume ( L , this . L , 0 ) ; } else { this . pushStackValue ( L , r ) ; lua . lua_resume ( L , this . L , 1 ) ; } } ) ; return lua . lua_yield ( L , 0 ) ; } else { return this . pushStackValue ( L , res ) ; } } ; Object . defineProperty ( func , 'name' , { value : key } ) ; return wrapped ; } private getStackValue ( L , index ) { if ( lua . lua_gettop ( L ) === 0 ) { return undefined ; } index = lua . lua_absindex ( L , index ) ; const type = lua . lua_type ( L , index ) ; switch ( type ) { case lua . LUA_TNUMBER : return lua . lua_tonumber ( L , index ) ; case lua . LUA_TSTRING : return lua . lua_tojsstring ( L , index ) ; case lua . LUA_TBOOLEAN : return lua . lua_toboolean ( L , index ) ; case lua . LUA_TUSERDATA : return lua . lua_touserdata ( L , index ) ; case lua . LUA_TFUNCTION : lua . lua_pushvalue ( L , index ) ; const cbRef = lauxlib . luaL_ref ( L , lua . LUA_REGISTRYINDEX ) ; return ( ... args ) : any => { const oldStackTop = lua . lua_gettop ( L ) ; lua . lua_rawgeti ( L , lua . LUA_REGISTRYINDEX , cbRef ) ; args . forEach ( p => { this . pushStackValue ( L , p ) ; } ) ; let ret = lua . lua_pcall ( L , args . length , lua . LUA_MULTRET , 0 ) ; if ( ret !== lua . LUA_OK ) { console . log ( `Error when exec function, ret=${ ret }, msg=${ this . getStackValue ( L , - 1 ) }` ) ; } ret = undefined ; if ( lua . lua_gettop ( L ) !== oldStackTop ) { ret = this . getStackValue ( L , - 1 ) ; } return ret ; } ; case lua . LUA_TTABLE : let v ; try { lua . lua_rawgeti ( L , index , 1 ) ; v = this . getStackValue ( L , - 1 ) ; lua . lua_pop ( L , 1 ) ; } catch { } if ( v !== null && v !== undefined ) { const arr = [ ] ; for ( let i = 1 ; ; i ++ ) { lua . lua_rawgeti ( L , index , i ) ; const v = this . getStackValue ( L , - 1 ) ; lua . lua_pop ( L , 1 ) ; if ( ! v ) break ; arr . push ( v ) ; } return arr ; } else { const ret = { } ; lua . lua_pushnil ( L ) ; while ( lua . lua_next ( L , index ) !== 0 ) { const value = this . getStackValue ( L , - 1 ) ; const key = this . getStackValue ( L , - 2 ) ; if ( value && key ) { ret [ key ] = value ; } lua . lua_pop ( L , 1 ) ; } return ret ; } case lua . LUA_TTHREAD : return lua . lua_tothread ( L , index ) ; case lua . LUA_TNONE : return undefined ; case lua . LUA_TNIL : return null ; default : console . log ( `Not supported type=${ type }, index=${ index }` ) ; return undefined ; } } private pushStackValue ( L , value , target ? ) { const type = typeof value ; switch ( type ) { case 'number' : lua . lua_pushnumber ( L , value ) ; break ; case 'string' : lua . lua_pushstring ( L , value ) ; break ; case 'boolean' : lua . lua_pushboolean ( L , value ) ; break ; case 'object' : if ( value === null || value === undefined ) { return 0 ; } else if ( Array . isArray ( value ) ) { lua . lua_newtable ( L ) ; ( value as any [ ] ) . forEach ( ( v , i ) => { const n = this . pushStackValue ( L , v ) ; if ( n !== 0 ) { lua . lua_rawseti ( L , - 2 , i + 1 ) ; } else { lua . lua_pop ( L , 1 ) ; } } ) ; } else if ( value instanceof Date ) { lua . lua_pushstring ( L , value . toDateString ( ) ) ; break ; } else { lua . lua_newtable ( L ) ; Object . keys ( value ) . forEach ( key => { lua . lua_pushstring ( L , key ) ; const v = value [ key ] ; let n = 0 ; if ( typeof v === 'function' ) { const f = this . wrapFunc ( v . name , v , target ) ; n = this . pushStackValue ( L , f , value ) ; } else { n = this . pushStackValue ( L , value [ key ] ) ; } if ( n === 0 ) { lua . lua_pop ( L , 1 ) ; } else { lua . lua_settable ( L , - 3 ) ; } } ) ; } break ; case 'function' : lua . lua_pushjsfunction ( L , value ) ; break ; case 'undefined' : return 0 ; default : console . log ( `Not supported type: ${ type }` ) ; return 0 ; } return 1 ; } public getLuaBuidinCodeLines ( ) { return luaBuidinCode . split ( '\n' ) . length ; } }