-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathindex.js
114 lines (105 loc) · 4.17 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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,
}