import { luaError , luaLenMetamethodSupported , luaRawLen } from "./luafuncs" ; import { Format } from "./format" ; import { Vars } from "./debugger" ; import { Thread , mainThread , mainThreadName } from "./thread" ; import { Breakpoint } from "./breakpoint" ; export namespace Send { const startToken = "@lldbg|" ; const endToken = "|lldbg@" ; const outputFileEnv = "LOCAL_LUA_DEBUGGER_OUTPUT_FILE" ; const outputFilePath = os . getenv ( outputFileEnv ) ; let outputFile ; if ( outputFilePath && outputFilePath . length > 0 ) { const [ file , err ] = io . open ( outputFilePath , "w+" ) ; if ( ! file ) { luaError ( `Failed to open output file "${ outputFilePath }": ${ err }\n` ) ; } outputFile = file as LuaFile ; outputFile . setvbuf ( "no" ) ; } else { outputFile = io . stdout ; } function getPrintableValue ( value ) { const valueType = type ( value ) ; if ( valueType === "string" ) { return `"${ value }"` ; } else if ( valueType === "number" || valueType === "boolean" || valueType === "nil" ) { return tostring ( value ) ; } else { const [ _ , str ] = pcall ( tostring , value ) ; const strType = type ( str ) ; if ( strType !== "string" ) { return `[${ strType }]` ; } return `[${ str }]` ; } } function isElementKey ( tbl , tblLen , key ) { return typeof key === "number" && key >= 1 && key <= tblLen ; } function buildVariable ( name , value ) { const dbgVar = { type : type ( value ) , name , value : getPrintableValue ( value ) } ; if ( typeof value === "object" ) { dbgVar . length = luaRawLen ( value as AnyTable ) ; } return dbgVar ; } function buildVarArgs ( name , values ) { const valueStrs = [ ] ; for ( const [ _ , val ] of ipairs ( values ) ) { table . insert ( valueStrs , getPrintableValue ( val ) ) ; } return { type : "table" , name , value : table . concat ( valueStrs , ", " ) , length : values . length } ; } function send ( message ) { outputFile . write ( `${ startToken }${ Format . asJson ( message ) }${ endToken }` ) ; } export function error ( err ) { const dbgError = { tag : "$luaDebug" , type : "error" , error : err } ; send ( dbgError ) ; } export function debugBreak ( message , breakType , threadId ) { const dbgBreak = { tag : "$luaDebug" , type : "debugBreak" , message , breakType , threadId } ; send ( dbgBreak ) ; } export function result ( ... values ) { const results = Format . makeExplicitArray ( ) ; for ( const value of values ) { table . insert ( results , { type : type ( value ) , value : getPrintableValue ( value ) } ) ; } const dbgResult = { tag : "$luaDebug" , type : "result" , results } ; send ( dbgResult ) ; } export function frames ( frameList ) { const dbgStack = { tag : "$luaDebug" , type : "stack" , frames : frameList } ; send ( dbgStack ) ; } export function threads ( threadIds , activeThread ) { const dbgThreads = { tag : "$luaDebug" , type : "threads" , threads : [ ] } ; for ( const [ thread , threadId ] of pairs ( threadIds ) ) { if ( thread === mainThread || coroutine . status ( thread as LuaThread ) !== "dead" ) { const dbgThread = { name : thread === mainThread ? mainThreadName : tostring ( thread ) , id : threadId , active : thread === activeThread || undefined } ; table . insert ( dbgThreads . threads , dbgThread ) ; } } send ( dbgThreads ) ; } export function vars ( varsObj ) { const dbgVariables = { tag : "$luaDebug" , type : "variables" , variables : Format . makeExplicitArray ( ) } ; for ( const [ name , info ] of pairs ( varsObj ) ) { const dbgVar = name === "..." ? buildVarArgs ( name , info . val as unknown [ ] ) : buildVariable ( name , info . val ) ; table . insert ( dbgVariables . variables , dbgVar ) ; } send ( dbgVariables ) ; } export function props ( tbl , kind ? , first ? , count ? ) { const dbgProperties = { tag : "$luaDebug" , type : "properties" , properties : Format . makeExplicitArray ( ) } ; if ( kind === "indexed" ) { first ??= 1 ; const last = count ? ( first + count - 1 ) : ( first + luaRawLen ( tbl ) - 1 ) ; for ( const i of $range ( first , last ) ) { const val = ( tbl as Record < string , unknown > ) [ i ] ; const name = getPrintableValue ( i ) ; const dbgVar = buildVariable ( name , val ) ; table . insert ( dbgProperties . properties , dbgVar ) ; } } else { const len = luaRawLen ( tbl ) ; for ( const [ key , val ] of pairs ( tbl ) ) { if ( kind !== "named" || ! isElementKey ( tbl , len , key ) ) { const name = getPrintableValue ( key ) ; const dbgVar = buildVariable ( name , val ) ; table . insert ( dbgProperties . properties , dbgVar ) ; } } const meta = getmetatable ( tbl ) ; if ( meta ) { dbgProperties . metatable = { type : type ( meta ) , value : getPrintableValue ( meta ) } ; } const [ lenStatus , tblLen ] = pcall ( ( ) => ( tbl as unknown [ ] ) . length as unknown ) ; if ( ! lenStatus ) { dbgProperties . length = { type : type ( tblLen ) , error : tblLen as string } ; } else if ( tblLen !== 0 ) { dbgProperties . length = { type : type ( tblLen ) , value : tostring ( tblLen ) } ; } else { const mt = debug . getmetatable ( tbl ) ; if ( ( ! mt && dbgProperties . properties . length === 0 ) || ( mt && luaLenMetamethodSupported && ( mt as { __len ? : unknown ; } ) . __len ) ) { dbgProperties . length = { type : type ( tblLen ) , value : tostring ( tblLen ) } ; } } } send ( dbgProperties ) ; } export function breakpoints ( breaks ) { const breakpointList = [ ] ; for ( const breakpoint of breaks ) { table . insert ( breakpointList , { line : breakpoint . sourceLine || breakpoint . line , file : breakpoint . sourceFile || breakpoint . file , condition : breakpoint . condition , enabled : breakpoint . enabled } ) ; } const dbgBreakpoints = { tag : "$luaDebug" , type : "breakpoints" , breakpoints : Format . makeExplicitArray ( breakpointList ) } ; send ( dbgBreakpoints ) ; } export function help ( ... helpStrs ) { let nameLength = 0 ; for ( const [ _ , nameAndDesc ] of ipairs ( helpStrs ) ) { nameLength = math . max ( nameLength , nameAndDesc [ 1 ] . length ) ; } const builtStrs = [ ] ; for ( const [ _ , nameAndDesc ] of ipairs ( helpStrs ) ) { const [ name , desc ] = unpack ( nameAndDesc ) ; table . insert ( builtStrs , `${ name }${ string . rep ( " " , nameLength - name . length + 1 ) }: ${ desc }` ) ; } outputFile . write ( `${ table . concat ( builtStrs , "\n" ) }\n` ) ; } }