forked from kcwiki/lua-json
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
128 lines (117 loc) · 4.58 KB
/
index.js
File metadata and controls
128 lines (117 loc) · 4.58 KB
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const { isNull, isBoolean, isNumber, isString, isArray, isObject, isEmpty, fromPairs, keys, map, repeat, property } = require('lodash')
const { parse: parseLua } = require('luaparse')
const formatLuaString = (string, singleQuote) => (singleQuote ? `'${string.replace(/'/g, "\\'")}'` : `"${string.replace(/"/g, '\\"')}"`)
const formatLuaKey = (string, singleQuote) => (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 => {
return `${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)
const res = `${eol}${keys(value)
.map(key => `${spaces}${formatLuaKey(key, options.singleQuote)} = ${rec(value[key], i + 1)},`)
.join(eol)}${eol}${spacesEnd}`;
return value.type === 'property'
? `[${isString(value.key) ? `'${value.key}'` : value.key }] = ${rec(value.value, i + 1)}`
: `{${res}}`;
}
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, escape(value)]
}),
)
return isEmpty(object) ? [] : object
}
return ast.fields
.filter(field => luaAstToJson(field) !== null)
.map(field => {
const value = luaAstToJson(field);
return value.__internal_table_key ? { type: 'property', key: value.key, value: escape(value.value) } : escape(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 }))
const escape = value => value && value.replace
? value
.replace(/\\\\/gm, '\\\\\\\\')
.replace(/\\r/gm, '\\\\r')
.replace(/\\n/gm, '\\\\n')
.replace(/\n/gm, '\\n')
: value;
module.exports = {
format,
parse,
}