115 lines
4.2 KiB
JavaScript
115 lines
4.2 KiB
JavaScript
const { isNull, isBoolean, isNumber, isString, isArray, isObject, isEmpty, fromPairs, keys, map, repeat } = require('lodash')
|
|
const { parse: parseLua } = require('luaparse')
|
|
|
|
const formatLuaString = (string, singleQuote) => (singleQuote ? `'${string.replace(/'/g, "\\'")}'` : `"${string.replace(/"/g, '\\"')}"`)
|
|
|
|
const valueKeys = { false: 'false', true: 'true', null: 'nil' }
|
|
|
|
const formatLuaKey = (string, singleQuote) =>
|
|
valueKeys[string] ? `[${valueKeys[string]}]` : string.match(/^[a-zA-Z_][a-zA-Z_0-9]*$/) ? string : `[${formatLuaString(string, singleQuote)}]`
|
|
|
|
const format = (value, options = { eol: '\n', singleQuote: true, spaces: 2 }) => {
|
|
options = options || {}
|
|
const eol = (options.eol = isString(options.eol) ? options.eol : '\n')
|
|
options.singleQuote = isBoolean(options.singleQuote) ? options.singleQuote : true
|
|
options.spaces = isNull(options.spaces) || isNumber(options.spaces) || isString(options.spaces) ? options.spaces : 2
|
|
|
|
const rec = (value, i = 0) => {
|
|
if (isNull(value)) {
|
|
return 'nil'
|
|
}
|
|
if (isBoolean(value) || isNumber(value)) {
|
|
return value.toString()
|
|
}
|
|
if (isString(value)) {
|
|
return formatLuaString(value, options.singleQuote)
|
|
}
|
|
if (isArray(value)) {
|
|
if (isEmpty(value)) {
|
|
return '{}'
|
|
}
|
|
if (options.spaces) {
|
|
const spaces = isNumber(options.spaces) ? repeat(' ', options.spaces * (i + 1)) : repeat(options.spaces, i + 1)
|
|
const spacesEnd = isNumber(options.spaces) ? repeat(' ', options.spaces * i) : repeat(options.spaces, i)
|
|
return `{${eol}${value.map(e => `${spaces}${rec(e, i + 1)},`).join(eol)}${eol}${spacesEnd}}`
|
|
}
|
|
return `{${value.map(e => `${rec(e, i + 1)},`).join('')}}`
|
|
}
|
|
if (isObject(value)) {
|
|
if (isEmpty(value)) {
|
|
return '{}'
|
|
}
|
|
if (options.spaces) {
|
|
const spaces = isNumber(options.spaces) ? repeat(' ', options.spaces * (i + 1)) : repeat(options.spaces, i + 1)
|
|
const spacesEnd = isNumber(options.spaces) ? repeat(' ', options.spaces * i) : repeat(options.spaces, i)
|
|
return `{${eol}${keys(value)
|
|
.map(key => `${spaces}${formatLuaKey(key, options.singleQuote)} = ${rec(value[key], i + 1)},`)
|
|
.join(eol)}${eol}${spacesEnd}}`
|
|
}
|
|
return `{${keys(value)
|
|
.map(key => `${formatLuaKey(key, options.singleQuote)}=${rec(value[key], i + 1)},`)
|
|
.join('')}}`
|
|
}
|
|
throw new Error(`can't format ${typeof value}`)
|
|
}
|
|
|
|
return `return${options.spaces ? ' ' : ''}${rec(value)}`
|
|
}
|
|
|
|
const luaAstToJson = ast => {
|
|
// literals
|
|
if (['NilLiteral', 'BooleanLiteral', 'NumericLiteral', 'StringLiteral'].includes(ast.type)) {
|
|
return ast.value
|
|
}
|
|
// basic expressions
|
|
if (ast.type === 'UnaryExpression' && ast.operator === '-') {
|
|
return -luaAstToJson(ast.argument)
|
|
}
|
|
if (ast.type === 'Identifier') {
|
|
return ast.name
|
|
}
|
|
// tables
|
|
if (['TableKey', 'TableKeyString'].includes(ast.type)) {
|
|
return { __internal_table_key: true, key: luaAstToJson(ast.key), value: luaAstToJson(ast.value) }
|
|
}
|
|
if (ast.type === 'TableValue') {
|
|
return luaAstToJson(ast.value)
|
|
}
|
|
if (ast.type === 'TableConstructorExpression') {
|
|
if (ast.fields[0] && ast.fields[0].key) {
|
|
const object = fromPairs(
|
|
map(ast.fields, field => {
|
|
const { key, value } = luaAstToJson(field)
|
|
return [key, value]
|
|
}),
|
|
)
|
|
return isEmpty(object) ? [] : object
|
|
}
|
|
return map(ast.fields, field => {
|
|
const value = luaAstToJson(field)
|
|
return value.__internal_table_key ? [value.key, value.value] : value
|
|
})
|
|
}
|
|
// top-level statements, only looking at the first statement, either return or local
|
|
// todo: filter until return or local?
|
|
if (ast.type === 'LocalStatement') {
|
|
const values = ast.init.map(luaAstToJson)
|
|
return values.length === 1 ? values[0] : values
|
|
}
|
|
if (ast.type === 'ReturnStatement') {
|
|
const values = ast.arguments.map(luaAstToJson)
|
|
return values.length === 1 ? values[0] : values
|
|
}
|
|
if (ast.type === 'Chunk') {
|
|
return luaAstToJson(ast.body[0])
|
|
}
|
|
throw new Error(`can't parse ${ast.type}`)
|
|
}
|
|
|
|
const parse = value => luaAstToJson(parseLua(value, { comments: false }))
|
|
|
|
module.exports = {
|
|
format,
|
|
parse,
|
|
}
|