@jsonic/ini 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ini.d.ts +24 -0
- package/dist/ini.js +447 -0
- package/dist/ini.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +20 -31
- package/src/ini.ts +518 -0
- package/src/tsconfig.json +14 -0
- package/ini.d.ts +0 -5
- package/ini.js +0 -271
- package/ini.js.map +0 -1
- package/ini.min.js +0 -1
- package/ini.ts +0 -298
- package/jest.config.js +0 -8
package/src/ini.ts
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
/* Copyright (c) 2021-2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// Import Jsonic types used by plugin.
|
|
4
|
+
import { Jsonic, RuleSpec, NormAltSpec, Lex, makePoint, Token } from 'jsonic'
|
|
5
|
+
import { Hoover } from '@jsonic/hoover'
|
|
6
|
+
|
|
7
|
+
type InlineCommentOptions = {
|
|
8
|
+
// Whether inline comments are active. Default: false.
|
|
9
|
+
active?: boolean
|
|
10
|
+
// Characters that start an inline comment. Default: ['#', ';'].
|
|
11
|
+
chars?: string[]
|
|
12
|
+
// Escape mechanisms for literal comment characters in values.
|
|
13
|
+
escape?: {
|
|
14
|
+
// Allow \; and \# to produce literal ; and #. Default: true.
|
|
15
|
+
backslash?: boolean
|
|
16
|
+
// Require whitespace before comment char to trigger. Default: false.
|
|
17
|
+
whitespace?: boolean
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type IniOptions = {
|
|
22
|
+
multiline?: {
|
|
23
|
+
// Character before newline indicating continuation. Default: '\\'.
|
|
24
|
+
// Set to false to disable backslash continuation.
|
|
25
|
+
continuation?: string | false
|
|
26
|
+
// When true, a continuation line must be indented (leading whitespace).
|
|
27
|
+
// Indented lines continue the previous value even without a continuation char.
|
|
28
|
+
indent?: boolean
|
|
29
|
+
} | boolean
|
|
30
|
+
section?: {
|
|
31
|
+
// How to handle duplicate section headers. Default: 'merge'.
|
|
32
|
+
// 'merge': combine keys from all occurrences (last value wins for duplicate keys)
|
|
33
|
+
// 'override': last section occurrence replaces earlier ones entirely
|
|
34
|
+
// 'error': throw when a previously declared section header appears again
|
|
35
|
+
duplicate?: 'merge' | 'override' | 'error'
|
|
36
|
+
}
|
|
37
|
+
comment?: {
|
|
38
|
+
// Control inline comment behavior. Default: inactive.
|
|
39
|
+
inline?: InlineCommentOptions
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --- BEGIN EMBEDDED ini-grammar.jsonic ---
|
|
44
|
+
const grammarText = `
|
|
45
|
+
# INI Grammar Definition
|
|
46
|
+
# Parsed by a standard Jsonic instance and passed to jsonic.grammar()
|
|
47
|
+
# Function references (@ prefixed) are resolved against the refs map
|
|
48
|
+
|
|
49
|
+
{
|
|
50
|
+
options: rule: { start: ini exclude: jsonic }
|
|
51
|
+
options: lex: { emptyResult: {} }
|
|
52
|
+
options: fixed: token: { '#EQ': '=' '#DOT': '.' '#OB': null '#CB': null '#CL': null }
|
|
53
|
+
options: line: { check: '@line-check' }
|
|
54
|
+
options: number: { lex: false }
|
|
55
|
+
options: string: { lex: true chars: QUOTE_CHARS abandon: true }
|
|
56
|
+
options: text: { lex: false }
|
|
57
|
+
options: comment: def: {
|
|
58
|
+
hash: { eatline: true }
|
|
59
|
+
slash: null
|
|
60
|
+
multi: null
|
|
61
|
+
semi: { line: true start: ';' lex: true eatline: true }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
rule: ini: open: [
|
|
65
|
+
{ s: '#OS' p: table b: 1 }
|
|
66
|
+
{ s: ['#HK #ST #VL' '#EQ'] p: table b: 2 }
|
|
67
|
+
{ s: ['#HV' '#OS'] p: table b: 2 }
|
|
68
|
+
{ s: '#ZZ' }
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
rule: table: open: [
|
|
72
|
+
{ s: '#OS' p: dive }
|
|
73
|
+
{ s: ['#HK #ST #VL' '#EQ'] p: map b: 2 }
|
|
74
|
+
{ s: ['#HV' '#OS'] p: map b: 2 }
|
|
75
|
+
{ s: '#CS' p: map }
|
|
76
|
+
{ s: '#ZZ' }
|
|
77
|
+
]
|
|
78
|
+
rule: table: close: [
|
|
79
|
+
{ s: '#OS' r: table b: 1 }
|
|
80
|
+
{ s: '#CS' r: table a: '@table-close-dive' }
|
|
81
|
+
{ s: '#ZZ' }
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
rule: dive: open: [
|
|
85
|
+
{ s: ['#DK' '#DOT'] a: '@dive-push' p: dive }
|
|
86
|
+
{ s: '#DK' a: '@dive-push' }
|
|
87
|
+
]
|
|
88
|
+
rule: dive: close: [
|
|
89
|
+
{ s: '#CS' b: 1 }
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
rule: map: open: {
|
|
93
|
+
alts: [
|
|
94
|
+
{ s: ['#HK #ST #VL' '#EQ'] c: '@is-table-parent' p: pair b: 2 }
|
|
95
|
+
{ s: ['#HK #ST #VL'] c: '@is-table-parent' p: pair b: 1 }
|
|
96
|
+
]
|
|
97
|
+
inject: { append: true }
|
|
98
|
+
}
|
|
99
|
+
rule: map: close: [
|
|
100
|
+
{ s: '#OS' b: 1 }
|
|
101
|
+
{ s: '#ZZ' }
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
rule: pair: open: [
|
|
105
|
+
{ s: ['#HK #ST #VL' '#EQ'] c: '@is-table-grandparent' p: val a: '@pair-key-eq' }
|
|
106
|
+
{ s: '#HK' c: '@is-table-grandparent' a: '@pair-key-bool' }
|
|
107
|
+
]
|
|
108
|
+
rule: pair: close: [
|
|
109
|
+
{ s: ['#HK #ST #VL' '#CL'] c: '@is-table-grandparent' e: '@pair-close-err' }
|
|
110
|
+
{ s: ['#HK #ST #VL'] b: 1 r: pair }
|
|
111
|
+
{ s: '#OS' b: 1 }
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
`
|
|
115
|
+
// --- END EMBEDDED ini-grammar.jsonic ---
|
|
116
|
+
|
|
117
|
+
function Ini(jsonic: Jsonic, _options: IniOptions) {
|
|
118
|
+
// Resolve inline comment options.
|
|
119
|
+
const inlineComment = {
|
|
120
|
+
active: _options.comment?.inline?.active ?? false,
|
|
121
|
+
chars: _options.comment?.inline?.chars ?? ['#', ';'],
|
|
122
|
+
escape: {
|
|
123
|
+
backslash: _options.comment?.inline?.escape?.backslash ?? true,
|
|
124
|
+
whitespace: _options.comment?.inline?.escape?.whitespace ?? false,
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build Hoover end.fixed arrays based on inline comment config.
|
|
129
|
+
// When active without whitespace mode, include comment chars as terminators.
|
|
130
|
+
// When whitespace mode is on, the custom value matcher handles detection instead.
|
|
131
|
+
const inlineCharsInFixed =
|
|
132
|
+
inlineComment.active && !inlineComment.escape.whitespace
|
|
133
|
+
|
|
134
|
+
const eolEndFixed: string[] = ['\n', '\r\n']
|
|
135
|
+
if (inlineCharsInFixed) {
|
|
136
|
+
eolEndFixed.push(...inlineComment.chars)
|
|
137
|
+
}
|
|
138
|
+
eolEndFixed.push('')
|
|
139
|
+
|
|
140
|
+
const keyEndFixed: string[] = ['=', '\n', '\r\n']
|
|
141
|
+
if (inlineCharsInFixed) {
|
|
142
|
+
keyEndFixed.push(...inlineComment.chars)
|
|
143
|
+
}
|
|
144
|
+
keyEndFixed.push('')
|
|
145
|
+
|
|
146
|
+
// Build escape maps. Always include '\\' -> '\\'.
|
|
147
|
+
// Add comment char escapes when inline comments are active with backslash escaping.
|
|
148
|
+
const eolEscape: Record<string, string> = { '\\': '\\' }
|
|
149
|
+
const keyEscape: Record<string, string> = { '\\': '\\' }
|
|
150
|
+
if (inlineComment.active && inlineComment.escape.backslash) {
|
|
151
|
+
for (const ch of inlineComment.chars) {
|
|
152
|
+
eolEscape[ch] = ch
|
|
153
|
+
keyEscape[ch] = ch
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
jsonic.use(Hoover, {
|
|
158
|
+
lex: {
|
|
159
|
+
order: 8.5e6,
|
|
160
|
+
},
|
|
161
|
+
block: {
|
|
162
|
+
endofline: {
|
|
163
|
+
start: {
|
|
164
|
+
rule: {
|
|
165
|
+
parent: {
|
|
166
|
+
include: ['pair', 'elem'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
end: {
|
|
171
|
+
fixed: eolEndFixed,
|
|
172
|
+
consume: ['\n', '\r\n'],
|
|
173
|
+
},
|
|
174
|
+
escapeChar: '\\',
|
|
175
|
+
escape: eolEscape,
|
|
176
|
+
allowUnknownEscape: true,
|
|
177
|
+
preserveEscapeChar: true,
|
|
178
|
+
trim: true,
|
|
179
|
+
},
|
|
180
|
+
key: {
|
|
181
|
+
token: '#HK',
|
|
182
|
+
start: {
|
|
183
|
+
rule: {
|
|
184
|
+
current: {
|
|
185
|
+
exclude: ['dive'],
|
|
186
|
+
},
|
|
187
|
+
state: 'oc',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
end: {
|
|
191
|
+
fixed: keyEndFixed,
|
|
192
|
+
consume: false,
|
|
193
|
+
},
|
|
194
|
+
escape: keyEscape,
|
|
195
|
+
trim: true,
|
|
196
|
+
},
|
|
197
|
+
divekey: {
|
|
198
|
+
token: '#DK',
|
|
199
|
+
start: {
|
|
200
|
+
rule: {
|
|
201
|
+
current: {
|
|
202
|
+
include: ['dive'],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
end: {
|
|
207
|
+
fixed: [']', '.'],
|
|
208
|
+
consume: false,
|
|
209
|
+
},
|
|
210
|
+
escapeChar: '\\',
|
|
211
|
+
escape: {
|
|
212
|
+
']': ']',
|
|
213
|
+
'.': '.',
|
|
214
|
+
'\\': '\\',
|
|
215
|
+
},
|
|
216
|
+
allowUnknownEscape: true,
|
|
217
|
+
trim: true,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const dupSection = _options.section?.duplicate || 'merge'
|
|
223
|
+
|
|
224
|
+
// Track explicitly declared section paths per parse call.
|
|
225
|
+
// Cleared in the ini rule's bo handler, used in the table rule.
|
|
226
|
+
const declaredSections = new Set<string>()
|
|
227
|
+
|
|
228
|
+
const ST = jsonic.token.ST as number
|
|
229
|
+
|
|
230
|
+
// Named function references for declarative grammar definition.
|
|
231
|
+
const refs: Record<string, Function> = {
|
|
232
|
+
// State actions (used by rule bo/bc/ac handlers).
|
|
233
|
+
'@ini-bo': (r: any) => {
|
|
234
|
+
r.node = {}
|
|
235
|
+
declaredSections.clear()
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
'@table-bo': (r: any) => {
|
|
239
|
+
r.node = r.parent.node
|
|
240
|
+
|
|
241
|
+
if (r.prev.u.dive) {
|
|
242
|
+
let dive = r.prev.u.dive
|
|
243
|
+
// Use null char as separator to avoid collisions with dots in key names.
|
|
244
|
+
let sectionKey = dive.join('\x00')
|
|
245
|
+
let isDuplicate = declaredSections.has(sectionKey)
|
|
246
|
+
|
|
247
|
+
if (isDuplicate && dupSection === 'error') {
|
|
248
|
+
throw new Error(
|
|
249
|
+
'Duplicate section: [' + dive.join('.') + ']'
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (let dI = 0; dI < dive.length; dI++) {
|
|
254
|
+
if (dI === dive.length - 1 && isDuplicate && dupSection === 'override') {
|
|
255
|
+
// Override: replace the section object entirely.
|
|
256
|
+
r.node = r.node[dive[dI]] = {}
|
|
257
|
+
} else {
|
|
258
|
+
r.node = r.node[dive[dI]] = r.node[dive[dI]] || {}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
declaredSections.add(sectionKey)
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
'@table-bc': (r: any) => {
|
|
267
|
+
Object.assign(r.node, r.child.node)
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
'@val-ac': (r: any) => {
|
|
271
|
+
if (ST === r.o0.tin && "'" === r.o0.src[0]) {
|
|
272
|
+
try {
|
|
273
|
+
r.node = JSON.parse(r.node)
|
|
274
|
+
} catch (e) {
|
|
275
|
+
// Invalid JSON, just accept val as given
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (null != r.prev.u.ini_prev) {
|
|
280
|
+
r.prev.node = r.node = r.prev.o0.src + r.node
|
|
281
|
+
} else if (r.parent.u.ini_array) {
|
|
282
|
+
r.parent.u.ini_array.push(r.node)
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// Alt actions.
|
|
287
|
+
'@table-close-dive': (r: any) => (r.u.dive = r.child.u.dive),
|
|
288
|
+
'@dive-push': (r: any) => (r.u.dive = r.parent.u.dive || []).push(r.o0.val),
|
|
289
|
+
|
|
290
|
+
'@pair-key-eq': (r: any) => {
|
|
291
|
+
let key = '' + r.o0.val
|
|
292
|
+
if (Array.isArray(r.node[key])) {
|
|
293
|
+
r.u.ini_array = r.node[key]
|
|
294
|
+
} else {
|
|
295
|
+
r.u.key = key
|
|
296
|
+
if (2 < key.length && key.endsWith('[]')) {
|
|
297
|
+
key = r.u.key = key.slice(0, -2)
|
|
298
|
+
r.node[key] = r.u.ini_array = Array.isArray(r.node[key])
|
|
299
|
+
? r.node[key]
|
|
300
|
+
: undefined === r.node[key]
|
|
301
|
+
? []
|
|
302
|
+
: [r.node[key]]
|
|
303
|
+
} else {
|
|
304
|
+
r.u.pair = true
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
'@pair-key-bool': (r: any) => {
|
|
310
|
+
let key = r.o0.val
|
|
311
|
+
if ('string' === typeof key && 0 < key.length) {
|
|
312
|
+
r.parent.node[key] = true
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
'@val-empty': (r: any) => (r.node = ''),
|
|
317
|
+
|
|
318
|
+
// Conditions.
|
|
319
|
+
'@is-table-parent': (r: any) => 'table' === r.parent.name,
|
|
320
|
+
'@is-table-grandparent': (r: any) => 'table' === r.parent.parent.name,
|
|
321
|
+
|
|
322
|
+
// Error handlers.
|
|
323
|
+
'@pair-close-err': (r: any) => r.c1,
|
|
324
|
+
|
|
325
|
+
// Options callbacks.
|
|
326
|
+
'@line-check': (lex: Lex) => {
|
|
327
|
+
if ('val' === lex.ctx.rule.name) {
|
|
328
|
+
return { done: true, token: undefined }
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Parse embedded grammar definition using a separate standard Jsonic instance.
|
|
334
|
+
const grammarDef = Jsonic.make()(grammarText)
|
|
335
|
+
grammarDef.ref = refs
|
|
336
|
+
grammarDef.options.string.chars = `'"`
|
|
337
|
+
jsonic.grammar(grammarDef)
|
|
338
|
+
|
|
339
|
+
// Custom value lex matcher.
|
|
340
|
+
// Needed when: (a) multiline continuation is enabled, or
|
|
341
|
+
// (b) inline comments are active with whitespace-prefix detection.
|
|
342
|
+
// Runs at higher priority than Hoover's endofline block to intercept values.
|
|
343
|
+
const multiline = true === _options.multiline ? {} : _options.multiline
|
|
344
|
+
const needCustomMatcher =
|
|
345
|
+
!!multiline || (inlineComment.active && inlineComment.escape.whitespace)
|
|
346
|
+
|
|
347
|
+
if (needCustomMatcher) {
|
|
348
|
+
const continuation: string | false = multiline
|
|
349
|
+
? (multiline.continuation !== undefined ? multiline.continuation : '\\')
|
|
350
|
+
: false
|
|
351
|
+
const indent = multiline ? (multiline.indent || false) : false
|
|
352
|
+
const HV_TIN = jsonic.token('#HV') as number
|
|
353
|
+
|
|
354
|
+
// Build a Set for fast comment char lookup in the matcher.
|
|
355
|
+
const commentCharSet = new Set(inlineComment.chars)
|
|
356
|
+
|
|
357
|
+
jsonic.options({
|
|
358
|
+
lex: {
|
|
359
|
+
match: {
|
|
360
|
+
multiline: {
|
|
361
|
+
// Lower order than Hoover (8.5e6) so this runs first.
|
|
362
|
+
order: 8.4e6,
|
|
363
|
+
make: () => {
|
|
364
|
+
return function multilineMatcher(lex: Lex): Token | undefined {
|
|
365
|
+
// Only match in value context during rule open state
|
|
366
|
+
// (same as Hoover endofline block, which defaults to state 'o').
|
|
367
|
+
let ctx = (lex as any).ctx
|
|
368
|
+
let parentName = ctx?.rule?.parent?.name
|
|
369
|
+
if (parentName !== 'pair' && parentName !== 'elem') {
|
|
370
|
+
return undefined
|
|
371
|
+
}
|
|
372
|
+
if (ctx?.rule?.state !== 'o') {
|
|
373
|
+
return undefined
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let src = lex.src
|
|
377
|
+
let sI = lex.pnt.sI
|
|
378
|
+
let rI = lex.pnt.rI
|
|
379
|
+
let cI = lex.pnt.cI
|
|
380
|
+
let startI = sI
|
|
381
|
+
let chars: string[] = []
|
|
382
|
+
|
|
383
|
+
while (sI < src.length) {
|
|
384
|
+
let c = src[sI]
|
|
385
|
+
|
|
386
|
+
// Check for inline comment characters (end value).
|
|
387
|
+
if (inlineComment.active && commentCharSet.has(c)) {
|
|
388
|
+
if (inlineComment.escape.whitespace) {
|
|
389
|
+
// Only treat as comment if preceded by whitespace.
|
|
390
|
+
if (
|
|
391
|
+
chars.length > 0 &&
|
|
392
|
+
(chars[chars.length - 1] === ' ' ||
|
|
393
|
+
chars[chars.length - 1] === '\t')
|
|
394
|
+
) {
|
|
395
|
+
break
|
|
396
|
+
}
|
|
397
|
+
// Not preceded by whitespace: treat as literal.
|
|
398
|
+
chars.push(c)
|
|
399
|
+
sI++; cI++
|
|
400
|
+
continue
|
|
401
|
+
}
|
|
402
|
+
break
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Check for backslash continuation before newline.
|
|
406
|
+
if (false !== continuation && c === continuation) {
|
|
407
|
+
if (src[sI + 1] === '\n') {
|
|
408
|
+
// \<LF> continuation
|
|
409
|
+
sI += 2; rI++; cI = 0
|
|
410
|
+
// Consume leading whitespace on continuation line.
|
|
411
|
+
while (sI < src.length &&
|
|
412
|
+
(src[sI] === ' ' || src[sI] === '\t')) {
|
|
413
|
+
sI++; cI++
|
|
414
|
+
}
|
|
415
|
+
continue
|
|
416
|
+
}
|
|
417
|
+
if (src[sI + 1] === '\r' && src[sI + 2] === '\n') {
|
|
418
|
+
// \<CR><LF> continuation
|
|
419
|
+
sI += 3; rI++; cI = 0
|
|
420
|
+
while (sI < src.length &&
|
|
421
|
+
(src[sI] === ' ' || src[sI] === '\t')) {
|
|
422
|
+
sI++; cI++
|
|
423
|
+
}
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Check for newline.
|
|
429
|
+
if (c === '\n' || (c === '\r' && src[sI + 1] === '\n')) {
|
|
430
|
+
// Indent continuation: next line starts with whitespace.
|
|
431
|
+
if (indent) {
|
|
432
|
+
let nextI = c === '\r' ? sI + 2 : sI + 1
|
|
433
|
+
if (nextI < src.length &&
|
|
434
|
+
(src[nextI] === ' ' || src[nextI] === '\t')) {
|
|
435
|
+
rI++; cI = 0
|
|
436
|
+
sI = nextI
|
|
437
|
+
// Consume leading whitespace.
|
|
438
|
+
while (sI < src.length &&
|
|
439
|
+
(src[sI] === ' ' || src[sI] === '\t')) {
|
|
440
|
+
sI++; cI++
|
|
441
|
+
}
|
|
442
|
+
chars.push(' ')
|
|
443
|
+
continue
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Normal newline: end value and consume the newline.
|
|
448
|
+
if (c === '\r') { sI += 2 } else { sI++ }
|
|
449
|
+
rI++; cI = 0
|
|
450
|
+
break
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Handle escape sequences.
|
|
454
|
+
if (c === '\\' && sI + 1 < src.length) {
|
|
455
|
+
let next = src[sI + 1]
|
|
456
|
+
if (
|
|
457
|
+
inlineComment.active &&
|
|
458
|
+
inlineComment.escape.backslash &&
|
|
459
|
+
commentCharSet.has(next)
|
|
460
|
+
) {
|
|
461
|
+
chars.push(next)
|
|
462
|
+
sI += 2; cI += 2
|
|
463
|
+
continue
|
|
464
|
+
}
|
|
465
|
+
if (next === '\\') {
|
|
466
|
+
chars.push('\\')
|
|
467
|
+
sI += 2; cI += 2
|
|
468
|
+
continue
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
chars.push(c)
|
|
473
|
+
sI++; cI++
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let val: string | undefined = chars.join('').trim()
|
|
477
|
+
|
|
478
|
+
let pnt = makePoint(lex.pnt.len, sI, rI, cI)
|
|
479
|
+
let tkn = lex.token(
|
|
480
|
+
HV_TIN, val, src.substring(startI, sI), pnt)
|
|
481
|
+
tkn.use = { block: 'endofline' }
|
|
482
|
+
|
|
483
|
+
lex.pnt.sI = sI
|
|
484
|
+
lex.pnt.rI = rI
|
|
485
|
+
lex.pnt.cI = cI
|
|
486
|
+
|
|
487
|
+
return tkn
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
})
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Val rule needs custom injection modifier not supported by grammar spec.
|
|
497
|
+
// Note: state actions (@ini-bo, @table-bo, @table-bc, @val-ac) are
|
|
498
|
+
// auto-applied by fnref() via the @rulename-{bo,ao,bc,ac} convention.
|
|
499
|
+
jsonic.rule('val', (rs: RuleSpec) => {
|
|
500
|
+
rs.fnref(refs)
|
|
501
|
+
.open(
|
|
502
|
+
[
|
|
503
|
+
// Since OS,CS are fixed tokens, concat them with string value
|
|
504
|
+
// if they appear as first char in a RHS value.
|
|
505
|
+
{ s: ['#OS #CS'], r: 'val', u: { ini_prev: true } },
|
|
506
|
+
{ s: '#ZZ', a: '@val-empty' },
|
|
507
|
+
],
|
|
508
|
+
{
|
|
509
|
+
custom: (alts: NormAltSpec[]) =>
|
|
510
|
+
alts.filter((alt: NormAltSpec) => alt.g.join() !== 'json,list'),
|
|
511
|
+
},
|
|
512
|
+
)
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export { Ini }
|
|
517
|
+
|
|
518
|
+
export type { IniOptions, InlineCommentOptions }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"esModuleInterop": true,
|
|
4
|
+
"module": "nodenext",
|
|
5
|
+
"noEmitOnError": true,
|
|
6
|
+
"outDir": "../dist",
|
|
7
|
+
"rootDir": ".",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"target": "ES2021"
|
|
13
|
+
}
|
|
14
|
+
}
|