mirror of
https://github.com/ershisan99/www.git
synced 2025-12-18 21:09:23 +00:00
update lua table parser
This commit is contained in:
241
src/app/(home)/log-parser/lua-parser.ts
Normal file
241
src/app/(home)/log-parser/lua-parser.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
type LuaValue = string | number | boolean | null | LuaTable | LuaValue[]
|
||||||
|
type LuaTable = { [key: string]: LuaValue }
|
||||||
|
|
||||||
|
export class LuaParser {
|
||||||
|
private pos = 0
|
||||||
|
private input = ''
|
||||||
|
private currentChar: string | null | undefined = null
|
||||||
|
|
||||||
|
constructor(input: string) {
|
||||||
|
this.input = input.trim()
|
||||||
|
this.currentChar = this.input[0] ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
private advance(): void {
|
||||||
|
this.pos++
|
||||||
|
this.currentChar =
|
||||||
|
this.pos < this.input.length ? this.input[this.pos] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
private skipWhitespace(): void {
|
||||||
|
while (
|
||||||
|
this.currentChar?.match(/\s/) !== null &&
|
||||||
|
this.currentChar !== null
|
||||||
|
) {
|
||||||
|
this.advance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseString(): string {
|
||||||
|
let result = ''
|
||||||
|
const quote = this.currentChar
|
||||||
|
this.advance() // Skip opening quote
|
||||||
|
|
||||||
|
while (this.currentChar !== null && this.currentChar !== quote) {
|
||||||
|
if (this.currentChar === '\\') {
|
||||||
|
this.advance()
|
||||||
|
switch (this.currentChar) {
|
||||||
|
case 'n':
|
||||||
|
result += '\n'
|
||||||
|
break
|
||||||
|
case 't':
|
||||||
|
result += '\t'
|
||||||
|
break
|
||||||
|
case 'r':
|
||||||
|
result += '\r'
|
||||||
|
break
|
||||||
|
case 'b':
|
||||||
|
result += '\b'
|
||||||
|
break
|
||||||
|
case 'f':
|
||||||
|
result += '\f'
|
||||||
|
break
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
case '\\':
|
||||||
|
result += this.currentChar
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
throw new Error(`Invalid escape sequence: \\${this.currentChar}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += this.currentChar
|
||||||
|
}
|
||||||
|
this.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentChar === null) {
|
||||||
|
throw new Error('Unterminated string')
|
||||||
|
}
|
||||||
|
this.advance() // Skip closing quote
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseNumber(): number {
|
||||||
|
let result = ''
|
||||||
|
|
||||||
|
if (this.currentChar === '-') {
|
||||||
|
result += this.currentChar
|
||||||
|
this.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
while (
|
||||||
|
this.currentChar?.match(/[\d.]/) !== null &&
|
||||||
|
this.currentChar !== null
|
||||||
|
) {
|
||||||
|
result += this.currentChar
|
||||||
|
this.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = Number.parseFloat(result)
|
||||||
|
if (isNaN(num)) {
|
||||||
|
throw new Error(`Invalid number: ${result}`)
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseIdentifier(): string {
|
||||||
|
let result = ''
|
||||||
|
|
||||||
|
while (
|
||||||
|
this.currentChar?.match(/[a-zA-Z0-9_]/) !== null &&
|
||||||
|
this.currentChar !== null
|
||||||
|
) {
|
||||||
|
result += this.currentChar
|
||||||
|
this.advance()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseValue(): LuaValue {
|
||||||
|
this.skipWhitespace()
|
||||||
|
|
||||||
|
if (this.currentChar === null) {
|
||||||
|
throw new Error('Unexpected end of input')
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.currentChar) {
|
||||||
|
case '{':
|
||||||
|
return this.parseTable()
|
||||||
|
case '"':
|
||||||
|
case "'":
|
||||||
|
return this.parseString()
|
||||||
|
case '-':
|
||||||
|
case '0':
|
||||||
|
case '1':
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
case '4':
|
||||||
|
case '5':
|
||||||
|
case '6':
|
||||||
|
case '7':
|
||||||
|
case '8':
|
||||||
|
case '9':
|
||||||
|
return this.parseNumber()
|
||||||
|
default: {
|
||||||
|
const identifier = this.parseIdentifier()
|
||||||
|
switch (identifier.toLowerCase()) {
|
||||||
|
case 'true':
|
||||||
|
return true
|
||||||
|
case 'false':
|
||||||
|
return false
|
||||||
|
case 'nil':
|
||||||
|
return null
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected identifier: ${identifier}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseTable(): LuaTable | LuaValue[] {
|
||||||
|
this.advance() // Skip '{'
|
||||||
|
this.skipWhitespace()
|
||||||
|
|
||||||
|
const result: LuaTable = {}
|
||||||
|
const array: LuaValue[] = []
|
||||||
|
let isArray = true
|
||||||
|
let index = 0
|
||||||
|
|
||||||
|
while (this.currentChar !== null && this.currentChar !== '}') {
|
||||||
|
this.skipWhitespace()
|
||||||
|
|
||||||
|
if (this.currentChar === '[') {
|
||||||
|
isArray = false
|
||||||
|
this.advance() // Skip '['
|
||||||
|
const key = this.parseValue()
|
||||||
|
if (typeof key !== 'string' && typeof key !== 'number') {
|
||||||
|
throw new Error('Table key must be string or number')
|
||||||
|
}
|
||||||
|
this.skipWhitespace()
|
||||||
|
|
||||||
|
if (this.currentChar !== ']') {
|
||||||
|
throw new Error("Expected ']'")
|
||||||
|
}
|
||||||
|
this.advance() // Skip ']'
|
||||||
|
this.skipWhitespace()
|
||||||
|
|
||||||
|
if (this.currentChar !== '=') {
|
||||||
|
throw new Error("Expected '='")
|
||||||
|
}
|
||||||
|
this.advance() // Skip '='
|
||||||
|
|
||||||
|
const value = this.parseValue()
|
||||||
|
result[String(key)] = value
|
||||||
|
} else {
|
||||||
|
const value = this.parseValue()
|
||||||
|
if (isArray) {
|
||||||
|
array.push(value)
|
||||||
|
index++
|
||||||
|
} else {
|
||||||
|
result[String(index)] = value
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.skipWhitespace()
|
||||||
|
if (this.currentChar === ',') {
|
||||||
|
this.advance()
|
||||||
|
} else if (this.currentChar !== '}') {
|
||||||
|
throw new Error("Expected ',' or '}'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.currentChar === null) {
|
||||||
|
throw new Error('Unterminated table')
|
||||||
|
}
|
||||||
|
this.advance() // Skip '}'
|
||||||
|
|
||||||
|
return isArray ? array : result
|
||||||
|
}
|
||||||
|
|
||||||
|
public parse(): LuaValue {
|
||||||
|
const result = this.parseValue()
|
||||||
|
this.skipWhitespace()
|
||||||
|
|
||||||
|
if (this.currentChar !== null) {
|
||||||
|
throw new Error('Unexpected characters after end of input')
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LuaToJsonConverter {
|
||||||
|
private static readonly logger = console
|
||||||
|
|
||||||
|
public static async convert(luaString: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const parser = new LuaParser(luaString)
|
||||||
|
const parsed = parser.parse()
|
||||||
|
return JSON.stringify(parsed, null, 2)
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('Error converting Lua to JSON:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
|
import { LuaToJsonConverter } from '@/app/(home)/log-parser/lua-parser'
|
||||||
import { OptimizedImage } from '@/components/optimized-image'
|
import { OptimizedImage } from '@/components/optimized-image'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@@ -37,7 +38,6 @@ import { jokers } from '@/shared/jokers'
|
|||||||
import { useFormatter } from 'next-intl'
|
import { useFormatter } from 'next-intl'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { type PvpBlind, PvpBlindsCard } from './_components/pvp-blinds'
|
import { type PvpBlind, PvpBlindsCard } from './_components/pvp-blinds'
|
||||||
|
|
||||||
// Define the structure for individual log events within a game
|
// Define the structure for individual log events within a game
|
||||||
type LogEvent = {
|
type LogEvent = {
|
||||||
timestamp: Date
|
timestamp: Date
|
||||||
@@ -1418,31 +1418,9 @@ type JsonValue =
|
|||||||
| JsonValue[]
|
| JsonValue[]
|
||||||
| { [key: string]: JsonValue }
|
| { [key: string]: JsonValue }
|
||||||
|
|
||||||
function luaTableToJson(luaString: string): string {
|
async function luaTableToJson(luaString: string): string {
|
||||||
let str = luaString.replace(/^return\s*/, '')
|
const str = luaString.replace(/^return\s*/, '')
|
||||||
|
return LuaToJsonConverter.convert(str)
|
||||||
// Handle nested tables closing properly
|
|
||||||
str = str.replace(/,(\s*})/g, '$1')
|
|
||||||
|
|
||||||
// Handle array-style table entries [1] = value
|
|
||||||
str = str.replace(/\[(\d+)\]\s*=/g, '"$1":')
|
|
||||||
|
|
||||||
// Handle string keys ["key"] = value
|
|
||||||
str = str.replace(/\[(["'][^"']+["'])\]\s*=/g, '$1:')
|
|
||||||
|
|
||||||
// Handle regular key = value
|
|
||||||
str = str.replace(/(\w+)\s*=/g, '"$1":')
|
|
||||||
|
|
||||||
// Replace single quotes with double quotes
|
|
||||||
str = str.replace(/'/g, '"')
|
|
||||||
|
|
||||||
// Clean up empty tables
|
|
||||||
str = str.replace(/{}/g, '{}')
|
|
||||||
|
|
||||||
// Remove trailing commas inside objects
|
|
||||||
str = str.replace(/,(\s*[}\]])/g, '$1')
|
|
||||||
|
|
||||||
return str
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decodePackedString(encodedString: string): Promise<JsonValue> {
|
async function decodePackedString(encodedString: string): Promise<JsonValue> {
|
||||||
@@ -1466,7 +1444,8 @@ async function decodePackedString(encodedString: string): Promise<JsonValue> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert Lua table to JSON
|
// Convert Lua table to JSON
|
||||||
const jsonString = luaTableToJson(decompressedString)
|
const jsonString = await luaTableToJson(decompressedString)
|
||||||
|
console.log(jsonString)
|
||||||
const result = JSON.parse(jsonString) as JsonValue
|
const result = JSON.parse(jsonString) as JsonValue
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1479,13 +1458,18 @@ async function decodePackedString(encodedString: string): Promise<JsonValue> {
|
|||||||
async function parseJokersFromString(str: string) {
|
async function parseJokersFromString(str: string) {
|
||||||
// Check if the string starts with 'H4' indicating a packed string
|
// Check if the string starts with 'H4' indicating a packed string
|
||||||
// This is a common prefix for base64 encoded gzip strings
|
// This is a common prefix for base64 encoded gzip strings
|
||||||
if (str.startsWith('H4')) {
|
try {
|
||||||
const decoded = await decodePackedString(str)
|
if (str.startsWith('H4')) {
|
||||||
if (decoded && typeof decoded === 'object' && 'cards' in decoded) {
|
const decoded = await decodePackedString(str)
|
||||||
return Object.values(decoded.cards as any).map(
|
if (decoded && typeof decoded === 'object' && 'cards' in decoded) {
|
||||||
(c: any) => c.save_fields.center
|
return Object.values(decoded.cards as any).map(
|
||||||
)
|
(c: any) => c.save_fields.center
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse jokers from string:', str, e)
|
||||||
|
return []
|
||||||
}
|
}
|
||||||
return str.split(';').filter(Boolean) // Remove empty strings if any
|
return str.split(';').filter(Boolean) // Remove empty strings if any
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user