-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.js
151 lines (119 loc) · 3.84 KB
/
mod.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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import {jevkoFromString as parseJevkoWithHeredocs} from 'https://cdn.jsdelivr.net/gh/jevko/jevko.js@v0.1.5/mod.js'
export const fromString = (str) => convert(parseJevkoWithHeredocs(str))
export const convert = (jevko) => nodes(prep(jevko))
const prep = jevko => {
const {subjevkos, ...rest} = jevko
const subs = []
for (const {prefix, jevko} of subjevkos) {
// todo: configurable linebreak
const lines = prefix.split('\n')
// discard all lines but last:
const trimmed = lines.at(-1).trim()
// discard all pairs that have name starting with -
if (trimmed.startsWith('-')) continue
subs.push({prefix: trimmed, jevko: prep(jevko)})
}
return {subjevkos: subs, ...rest}
}
const toKey = (jevko) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length === 0) {
const trimmed = suffix.trim()
if (trimmed === '') throw Error('empty key not allowed')
return trimmed
}
console.error(jevko)
throw Error('not a valid key')
}
// todo: rename
const nodes = (jevko) => {
const topMap = Object.create(null)
// or initial section is topMap
let currentSection = topMap
let currentSectionKey = ''
// topMap[currentSectionKey] = currentSection
// new Map([
// [currentSectionKey, currentSection],
// ])
const {subjevkos, suffix} = jevko
if (suffix.trim() !== '') throw Error('1')
for (const {prefix, jevko} of subjevkos) {
if (prefix === '') {
const {path, isRelative} = toPath(jevko)
if (isRelative === false) currentSection = topMap
for (const p of path) {
currentSectionKey = p
if (currentSectionKey in currentSection === false) {
currentSection[currentSectionKey] = Object.create(null)
}
currentSection = currentSection[currentSectionKey]
}
} else {
// note: allows overwriting
currentSection[prefix] = inner(jevko)
}
}
return topMap
}
const toPath = jevko => {
const {subjevkos, suffix} = jevko
if (subjevkos.length === 0) return {path: [toKey(jevko)], isRelative: false}
if (suffix.trim() !== '') throw Error('oops')
const {prefix, jevko: jevko0} = subjevkos[0]
const ret = []
let isRelative = false
if (prefix === './') {
isRelative = true
} else if (prefix !== '') throw Error('oops')
ret.push(toKey(jevko0))
for (const {prefix, jevko} of subjevkos.slice(1)) {
if (prefix !== '') throw Error('oops')
ret.push(toKey(jevko))
}
return {path: ret, isRelative}
}
const inner = (jevko) => {
const {subjevkos, suffix} = jevko
if (subjevkos.length === 0) {
const {tag} = jevko
if (tag === 'json') return JSON.parse(suffix)
const trimmed = suffix.trim()
if (trimmed.startsWith("'")) {
// note: allow unclosed string literals
if (trimmed.at(-1) === "'") return trimmed.slice(1, -1)
return trimmed.slice(1)
}
if (trimmed === 'true') return true
if (trimmed === 'false') return false
if (trimmed === 'null') return null
if (trimmed === 'map') return Object.create(null)
if (trimmed === 'list') return []
if (trimmed === 'NaN') return NaN
const num = Number(trimmed)
if (Number.isNaN(num) === false) return num
// todo: recognize different primitive types:
// numbers, 'strings, bools, null, regular strings, =list, =map
return suffix
}
if (suffix.trim() !== '') throw Error('oops')
const sub0 = subjevkos[0]
if (sub0.prefix === '') return list(subjevkos)
return map(subjevkos)
}
const list = subjevkos => {
const ret = []
for (const {prefix, jevko} of subjevkos) {
if (prefix !== '') throw Error('oops')
ret.push(inner(jevko))
}
return ret
}
const map = subjevkos => {
const ret = Object.create(null)
for (const {prefix, jevko} of subjevkos) {
if (prefix === '') throw Error('oops')
if (prefix in ret) throw Error('dupe')
ret[prefix] = inner(jevko)
}
return ret
}