chipmunkbot3/util/snbt.js
2024-02-11 21:23:41 -05:00

117 lines
3.6 KiB
JavaScript

const quotes = ['"', "'"]
function stringify (nbt, space = false) {
const sep = ',' + (space ? ' ' : '')
switch (nbt.type) {
case 'byte':
return nbt.value + 'b'
case 'short':
return nbt.value + 's'
case 'int':
return nbt.value.toString()
case 'long':
return nbt.value + 'l'
case 'float':
return nbt.value + 'f'
case 'double':
return nbt.value + 'd'
case 'string':
return stringifyStr(nbt.value, true)
case 'list':
return `[${
nbt.value.value
.map((tag) => stringify({ type: nbt.value.type, value: tag }, space))
.join(sep)
}]`
case 'compound':
return `{${Object.entries(nbt.value).map(([key, prop]) =>
stringifyStr(key, false) + ':' + (space ? ' ' : '') + stringify(prop)
).join(sep)}}`
case 'byteArray':
return `[B;${space ? ' ' : ''}${
nbt.value
.map((value) => stringify({ type: 'byte', value }))
.join(sep)
}]`
case 'intArray':
return `[I;${space ? ' ' : ''}${
nbt.value
.map((value) => stringify({ type: 'int', value }))
.join(sep)
}]`
case 'longArray':
return `[L;${space ? ' ' : ''}${
nbt.value
.map((value) => stringify({ type: 'long', value }))
.join(sep)
}]`
case 'bool':
return nbt.value.toString()
default:
throw new Error('Unknown type: ' + nbt.type)
}
function stringifyStr (str, forceQuotes = false) {
const regex = /['\\]/g
if (regex.test(str) || /[\s|"]/g.test(str) || forceQuotes) {
str = `'${str.replace(regex, (m) => '\\' + m)}'`
}
return str
}
}
function parse (snbt) {
const num = parseInt(snbt)
if (!isNaN(num)) {
if (snbt.endsWith('b')) {
return { type: 'byte', value: num }
} else if (snbt.endsWith('s')) {
return { type: 'short', value: num }
} else if (snbt.endsWith('l')) {
return { type: 'long', value: num }
} else if (snbt.endsWith('f')) {
return { type: 'float', value: num }
} else if (snbt.endsWith('d')) {
return { type: 'double', value: num }
} else {
return { type: 'int', value: num }
}
} else if (snbt === 'true') { return true } else if (snbt === 'false') { return false } else if (snbt.startsWith('{') && snbt.endsWith('}')) {
const entries = split(snbt.slice(1, -1), ',').map(e => split(e, ':')).map(([key, prop]) => [parseStr(key), parse(prop)])
const obj = {}
entries.forEach(([key, prop]) => (obj[key] = prop))
return { type: 'compound', value: obj }
// } else if (snbt.startsWith('[') && snbt.endsWith(']')) {
} else {
return { type: 'string', value: parseStr(snbt) }
}
// worst string parser ever
function parseStr (str) {
const unquotedWl = /[a-zA-Z0-9+\-.]/g
// check quotes
for (const quote of quotes) {
if (str.startsWith(quote) && str.startsWith(quote)) {
return str.replace(new RegExp(`\\\\|\\${quote}`, 'g'), (m) => m.slice(1))
}
}
// if (!unquotedWl.test(str)) throw new Error('Invalid character in unquoted string: ' + str)
return str
}
function split (str, char, times = Infinity) {
let quote = null
let split = ''
const result = []
for (let i = 0; i < str.length; i++) {
if (quotes.includes(str[i]) && str[i - 1] !== '\\') {
if (!quote) { quote = str[i] } else if (str[i] === quote) { quote = null }
} else if (str[i] === char || !quote) {
result.push(split.trim())
split = ''
} else {
split += str[i]
}
}
return [...result, split]
}
}
module.exports = { stringify, parse }