yini-parser 1.0.1-beta → 1.1.0-beta

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +131 -328
  3. package/dist/YINI.d.ts +34 -11
  4. package/dist/YINI.js +206 -121
  5. package/dist/core/ASTBuilder.d.ts +191 -0
  6. package/dist/core/ASTBuilder.js +827 -0
  7. package/dist/core/ErrorDataHandler.d.ts +19 -19
  8. package/dist/core/ErrorDataHandler.js +258 -150
  9. package/dist/core/objectBuilder.d.ts +9 -3
  10. package/dist/core/objectBuilder.js +126 -163
  11. package/dist/core/types.d.ts +234 -44
  12. package/dist/core/types.js +7 -33
  13. package/dist/grammar/YiniLexer.d.ts +54 -48
  14. package/dist/grammar/YiniLexer.js +330 -293
  15. package/dist/grammar/YiniParser.d.ts +167 -150
  16. package/dist/grammar/YiniParser.js +1241 -1202
  17. package/dist/grammar/YiniParserVisitor.d.ts +59 -45
  18. package/dist/grammar/YiniParserVisitor.js +1 -1
  19. package/dist/index.d.ts +4 -2
  20. package/dist/index.js +298 -120
  21. package/dist/parseEntry.d.ts +3 -2
  22. package/dist/parseEntry.js +352 -81
  23. package/dist/parsers/extractHeaderParts.d.ts +3 -2
  24. package/dist/parsers/extractHeaderParts.js +1 -0
  25. package/dist/parsers/parseBoolean.d.ts +1 -1
  26. package/dist/parsers/parseBoolean.js +2 -1
  27. package/dist/parsers/parseNumber.d.ts +8 -3
  28. package/dist/parsers/parseNumber.js +50 -16
  29. package/dist/parsers/parseSectionHeader.d.ts +3 -2
  30. package/dist/parsers/parseSectionHeader.js +1 -0
  31. package/dist/utils/number.d.ts +3 -0
  32. package/dist/utils/number.js +18 -0
  33. package/dist/utils/object.d.ts +55 -0
  34. package/dist/utils/object.js +85 -0
  35. package/dist/utils/string.d.ts +21 -1
  36. package/dist/utils/string.js +39 -4
  37. package/dist/utils/system.d.ts +15 -0
  38. package/dist/utils/system.js +21 -0
  39. package/dist/yiniHelpers.d.ts +3 -0
  40. package/dist/yiniHelpers.js +43 -7
  41. package/package.json +3 -3
  42. package/dist/core/YINIVisitor.d.ts +0 -158
  43. package/dist/core/YINIVisitor.js +0 -1010
@@ -0,0 +1,827 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const env_1 = require("../config/env");
7
+ const YiniParserVisitor_1 = __importDefault(require("../grammar/YiniParserVisitor"));
8
+ const extractSignificantYiniLine_1 = require("../parsers/extractSignificantYiniLine");
9
+ const parseBoolean_1 = __importDefault(require("../parsers/parseBoolean"));
10
+ const parseNumber_1 = __importDefault(require("../parsers/parseNumber"));
11
+ // import parseNumber from '../parsers/parseNumber'
12
+ const parseSectionHeader_1 = __importDefault(require("../parsers/parseSectionHeader"));
13
+ const number_1 = require("../utils/number");
14
+ const print_1 = require("../utils/print");
15
+ const string_1 = require("../utils/string");
16
+ const yiniHelpers_1 = require("../yiniHelpers");
17
+ const ErrorDataHandler_1 = require("./ErrorDataHandler");
18
+ // -----------------------
19
+ // --- Helpers -------------------------------------------------------------
20
+ // let _subjectType: TSubjectType = 'None'
21
+ let _sourceType;
22
+ /**
23
+ * @param {string | undefined} [tag]
24
+ * Debugging only. Its contents may change at any time and
25
+ * must not be relied upon for any functional purpose.
26
+ */
27
+ const makeScalarValue = (type, value = null, tag = undefined) => {
28
+ switch (type) {
29
+ case 'String':
30
+ return { type, value: value, tag };
31
+ case 'Number':
32
+ return { type, value: value, tag };
33
+ case 'Boolean':
34
+ return { type, value: !!value, tag };
35
+ case 'Null':
36
+ return { type: 'Null', value: null, tag };
37
+ case 'Undefined':
38
+ return { type: 'Undefined', value: undefined, tag };
39
+ default:
40
+ new ErrorDataHandler_1.ErrorDataHandler(_sourceType).pushOrBail(null, 'Fatal-Error', `No such type in makeValue(..), type: ${type}, value: ${value}`, 'Something in the code is done incorrectly in order for this to happen... :S');
41
+ }
42
+ return { type: 'Null', value: null, tag };
43
+ };
44
+ /**
45
+ * @param {string | undefined} [tag]
46
+ * Debugging only. Its contents may change at any time and
47
+ * must not be relied upon for any functional purpose.
48
+ */
49
+ const makeListValue = (elems = [], tag = undefined) => {
50
+ return { type: 'List', elems, tag };
51
+ };
52
+ /**
53
+ * @param {string | undefined} [tag]
54
+ * Debugging only. Its contents may change at any time and
55
+ * must not be relied upon for any functional purpose.
56
+ */
57
+ const makeObjectValue = (entries = {}, tag = undefined) => {
58
+ return { type: 'Object', entries, tag };
59
+ };
60
+ function trimQuotes(text) {
61
+ // STRING token already excludes quotes; the rule returns the literal with quotes present.
62
+ // We’ll reliably strip the outer quote(s) and leave contents as-is (concat pieces handled below).
63
+ const q = text[0];
64
+ if ((q === '"' || q === "'") &&
65
+ text.length >= 2 &&
66
+ text[text.length - 1] === q) {
67
+ return text.slice(1, -1);
68
+ }
69
+ // Triple-quoted cases are handled by the lexer too; same stripping works since token text begins with quotes.
70
+ if (text.startsWith('"""') && text.endsWith('"""') && text.length >= 6) {
71
+ return text.slice(3, -3);
72
+ }
73
+ return text;
74
+ }
75
+ function makeSection(name, level) {
76
+ return { sectionName: name, level, members: new Map(), children: [] };
77
+ }
78
+ /** Parse SECTION_HEAD token text → {level, name}.
79
+ * Supports repeated markers (^^^^) and shorthand (^7) (Spec 5.2–5.3.1). :contentReference[oaicite:5]{index=5}:contentReference[oaicite:6]{index=6}
80
+ */
81
+ // function parseSectionHeadToken(raw: string): { level: number; name: string } {
82
+ // // SECTION_HEAD token text includes: optional WS, marker(s) or shorthand, WS, IDENT (possibly backticked), NL+
83
+ // // We only need the visible line content up to NL.
84
+ // const line = raw.split(/\r?\n/)[0]
85
+ // // Extract marker block and name
86
+ // // Examples: "^^ Section", "^7 `Section name`", "< MySection"
87
+ // const m = line.match(/^\s*([\^<§€]+|\^|\<|§|€)(\d+)?[ \t]+(.+?)\s*$/)
88
+ // if (m) {
89
+ // const markerRun = m[1]
90
+ // const numeric = m[2]
91
+ // let level: number
92
+ // if (numeric) {
93
+ // level = parseInt(numeric, 10)
94
+ // } else {
95
+ // // count repeated marker chars (^^^^)
96
+ // level = markerRun.length
97
+ // }
98
+ // // Section name may be backticked: `Name with spaces`
99
+ // let name = m[3]
100
+ // if (name.startsWith('`') && name.endsWith('`')) {
101
+ // name = name.slice(1, -1)
102
+ // }
103
+ // return { level, name }
104
+ // }
105
+ // // Fallback: be defensive
106
+ // return { level: 1, name: line.trim() }
107
+ // }
108
+ // --- Builder Visitor -----------------------------------------------------
109
+ /**
110
+ * This interface defines a complete generic visitor for a parse tree produced
111
+ * by `YiniParser`.
112
+ *
113
+ * @param <Result> The return type of the visit operation. Use `void` for
114
+ * operations with no return type.
115
+ */
116
+ // export default class YINIVisitor<IResult> extends YiniParserVisitor<IResult> {
117
+ class ASTBuilder extends YiniParserVisitor_1.default {
118
+ /**
119
+ * @param metaFileName If parsing from a file, provide the file name here so the meta information can be updated accordingly.
120
+ * @param metaLineCount Provide the line-count here so the meta information can be updated accordingly.
121
+ */
122
+ constructor(errorHandler, options, sourceType, metaFileName) {
123
+ super();
124
+ this.errorHandler = null;
125
+ this.meta_hasYiniMarker = false; // For stats.
126
+ // private meta_numOfSections = 0 // For stats.
127
+ this._numOfMembers = 0; // For error checking and stats.
128
+ // private meta_numOfChains = 0 // For stats.
129
+ this.meta_maxLevel = 0; // For stats.
130
+ this.mapSectionNamePaths = new Map();
131
+ // --- Private helper methods --------------------------------
132
+ this.hasDefinedSectionTitle = (keyPath) => {
133
+ var _a;
134
+ return (_a = this.mapSectionNamePaths) === null || _a === void 0 ? void 0 : _a.has(keyPath);
135
+ };
136
+ this.setDefineSectionTitle = (keyPath, level) => {
137
+ this.mapSectionNamePaths.set(keyPath, level);
138
+ };
139
+ /**
140
+ * Visit a parse tree produced by `YiniParser.yini`.
141
+ * @param ctx the parse tree
142
+ * @return the visitor result
143
+ */
144
+ // visitYini?: (ctx: YiniContext) => Result
145
+ // visitYini?: (ctx: YiniContext) => any
146
+ this.visitYini = (ctx) => {
147
+ var _a;
148
+ // children: prolog?, stmt*, terminal?, EOF
149
+ (_a = ctx.children) === null || _a === void 0 ? void 0 : _a.forEach((c) => { var _a; return (_a = this.visit) === null || _a === void 0 ? void 0 : _a.call(this, c); });
150
+ return this.ast;
151
+ };
152
+ /**
153
+ * Visit a parse tree produced by `YiniParser.prolog`.
154
+ * @param ctx the parse tree
155
+ * @return the visitor result
156
+ */
157
+ // visitProlog?: (ctx: PrologContext) => Result
158
+ this.visitProlog = (ctx) => {
159
+ // Ignored for structure; keeps column rules stable.
160
+ return null;
161
+ };
162
+ /**
163
+ * Visit a parse tree produced by `YiniParser.terminal`.
164
+ * @param ctx the parse tree
165
+ * @return the visitor result
166
+ */
167
+ // visitTerminal_stmt?: (ctx: Terminal_stmtContext) => Result;
168
+ this.visitTerminal_stmt = (ctx) => {
169
+ (0, print_1.debugPrint)('-> Entered visitTerminal_stmt(..)');
170
+ let rawText = ctx.getText().trim();
171
+ (0, print_1.debugPrint)('rawText = "' + rawText + '"');
172
+ // rawText = extractYiniLine(rawText) // Remove possible comments.
173
+ // rawText = stripCommentsAndAfter(rawText.split('\n', 1)[0]).trim() // Remove possible comments.
174
+ rawText = (0, yiniHelpers_1.stripCommentsAndAfter)(rawText); // Remove possible comments.
175
+ (0, print_1.debugPrint)('rawText2 = "' + rawText + '"');
176
+ if (rawText.toLowerCase() === '/end') {
177
+ // NOTE: Below, maybe not reached at all.
178
+ if (this.ast.terminatorSeen) {
179
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
180
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Warning', 'Hit a duplicate terminator in document', `'${rawText}' already exists in this file, there must only be one terminator at the end of file ('/END'). Also note that the terminator is optional in both lenient and strict mode, unless the option 'isRequireDocTerminator' is enabled.`);
181
+ }
182
+ }
183
+ else {
184
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
185
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Encountered unknow syntax for terminator', `Got '${rawText}', but expected '/END' (case insensitive).`);
186
+ }
187
+ this.ast.terminatorSeen = true;
188
+ return null;
189
+ };
190
+ /**
191
+ * Visit a parse tree produced by `YiniParser.stmt`.
192
+ * @param ctx the parse tree
193
+ * @grammarRule eol | SECTION_HEAD | assignment | colon_list_decl | marker_stmt | bad_member
194
+ * @return the visitor result
195
+ */
196
+ // visitStmt?: (ctx: StmtContext) => Result
197
+ this.visitStmt = (ctx) => {
198
+ var _a, _b, _c, _d, _e, _f, _g, _h;
199
+ const child = ctx.getChild(0);
200
+ const ruleName = (_b = (_a = child === null || child === void 0 ? void 0 : child.constructor) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '';
201
+ if (ruleName.includes('EolContext'))
202
+ return (_c = this.visitEol) === null || _c === void 0 ? void 0 : _c.call(this, child);
203
+ if (ruleName.includes('AssignmentContext'))
204
+ return (_d = this.visitAssignment) === null || _d === void 0 ? void 0 : _d.call(this, child);
205
+ if (ruleName.includes('Colon_list_declContext'))
206
+ return (_e = this.visitColon_list_decl) === null || _e === void 0 ? void 0 : _e.call(this, child);
207
+ if (ruleName.includes('Meta_stmtContext'))
208
+ return (_f = this.visitMeta_stmt) === null || _f === void 0 ? void 0 : _f.call(this, child);
209
+ (0, print_1.debugPrint)('S1');
210
+ // let headerAlt = child.getText?.() ?? ''
211
+ // let header = ctx.SECTION_HEAD()?.getText().trim() || ''
212
+ let header = ((_g = ctx.SECTION_HEAD()) === null || _g === void 0 ? void 0 : _g.getText().trim()) || '';
213
+ // debugPrint('S2, lineAlt: >>>' + lineAlt + '<<<')
214
+ (0, print_1.debugPrint)('S2, header: >>>' + header + '<<<');
215
+ header = (0, extractSignificantYiniLine_1.extractYiniLine)(header);
216
+ (0, print_1.debugPrint)('S3, header: >>>' + header + '<<<');
217
+ if (!!header) {
218
+ const { sectionName, sectionLevel } = (0, parseSectionHeader_1.default)(header, this.errorHandler, ctx);
219
+ // Validate level sequencing per spec 5.3 (no skipping upward)
220
+ const currentLevel = this.sectionStack[this.sectionStack.length - 1].level;
221
+ if (sectionLevel > currentLevel + 1) {
222
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
223
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid section level transition', `Cannot skip levels: from ${currentLevel} to ${sectionLevel}.`, `Section headers may not start directly at level ${sectionLevel}, skipping previous section levels. Please start with one level further down.`);
224
+ }
225
+ const section = makeSection((0, string_1.trimBackticks)(sectionName), sectionLevel);
226
+ this.attachSection(ctx, this.sectionStack, section, this.ast); // respects up/down nesting
227
+ return null;
228
+ }
229
+ // bad_member fallback
230
+ return (_h = this.visitBad_member) === null || _h === void 0 ? void 0 : _h.call(this, ctx.getChild(0));
231
+ };
232
+ /**
233
+ * Visit a parse tree produced by `YiniParser.meta_stmt`.
234
+ * @param ctx the parse tree
235
+ */
236
+ this.visitMeta_stmt = (ctx) => {
237
+ var _a;
238
+ (0, print_1.debugPrint)('-> Entered visitMeta_stmt(..)');
239
+ let rawText = ctx.getText().trim();
240
+ (0, print_1.debugPrint)('rawText = "' + rawText + '"');
241
+ (_a = ctx.children) === null || _a === void 0 ? void 0 : _a.forEach((c) => {
242
+ var _a;
243
+ let rawText = c === null || c === void 0 ? void 0 : c.getText().trim();
244
+ (0, print_1.debugPrint)('visitMeta_stmt:child = "' + rawText + '"');
245
+ (_a = this.visit) === null || _a === void 0 ? void 0 : _a.call(this, c);
246
+ });
247
+ return null;
248
+ };
249
+ /**
250
+ * Visit a parse tree produced by `YiniParser.directive`.
251
+ * @param ctx the parse tree
252
+ * @note Directive statements in YINI are special top-level commands that
253
+ * appear only at the beginning of a document, before any sections
254
+ * or members. Each directive may occur at most once per file.
255
+ */
256
+ this.visitDirective = (ctx) => {
257
+ (0, print_1.debugPrint)('-> Entered visitDirective(..)');
258
+ let rawText = ctx.getText().trim();
259
+ (0, print_1.debugPrint)('rawText = "' + rawText + '"');
260
+ // NOTE: Important to strip any possible comments on the same line.
261
+ rawText = (0, yiniHelpers_1.stripCommentsAndAfter)(rawText); // Remove possible comments.
262
+ (0, print_1.debugPrint)('rawText2 = "' + rawText + '"');
263
+ if (this.mapSectionNamePaths.size || this._numOfMembers) {
264
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
265
+ this.errorHandler.pushOrBail(ctx, this.isStrict ? 'Syntax-Error' : 'Syntax-Warning', `Found a directive statement in the wrong place ${this.isStrict ? '(strict mode)' : '(lenient mode)'}`, `Directive '${rawText}' must appear only at the beginning of the document, before any sections or members.`, `Tip: Move the line with '${rawText}' to the very top of the file (but after a possible #! line or comments).`);
266
+ }
267
+ if (rawText.toLowerCase().startsWith('@include')) {
268
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
269
+ this.errorHandler.pushOrBail(ctx, 'Notice', `Detected unsupported directive '@include'`, `This directive is not currently supported by the parser.`);
270
+ }
271
+ else if (rawText.toLowerCase() === '@yini') {
272
+ if (this.ast.yiniMarkerSeen) {
273
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
274
+ this.errorHandler.pushOrBail(ctx, this.isStrict ? 'Syntax-Error' : 'Syntax-Warning', `Hit a duplicate YINI Marker in document`, `'${rawText}' already exists in this file, it's enough with only one YINI Marker ('@YINI').`);
275
+ }
276
+ }
277
+ else {
278
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
279
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Encountered unknow directive statement', `Got '${rawText}', but expected '@YINI' (case insensitive).`);
280
+ }
281
+ // @yini marker is advisory (no semantic value) per spec. We ignore it. (Spec 2.4) :contentReference[oaicite:9]{index=9}
282
+ this.ast.yiniMarkerSeen = true;
283
+ return null;
284
+ };
285
+ /**
286
+ * Visit a parse tree produced by `YiniParser.annotation`.
287
+ * @param ctx the parse tree
288
+ * @return the visitor result
289
+ */
290
+ this.visitAnnotation = (ctx) => {
291
+ /*
292
+
293
+ Experimental / for future.
294
+
295
+ */
296
+ (0, print_1.debugPrint)('-> Entered visitAnnotation(..)');
297
+ let rawText = ctx.getText().trim();
298
+ (0, print_1.debugPrint)('rawText = "' + rawText + '"');
299
+ // NOTE: Important to strip any possible comments on the same line.
300
+ rawText = (0, yiniHelpers_1.stripCommentsAndAfter)(rawText); // Remove possible comments.
301
+ (0, print_1.debugPrint)('rawText2 = "' + rawText + '"');
302
+ if (rawText.toLowerCase().startsWith('@deprecated')) {
303
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
304
+ this.errorHandler.pushOrBail(ctx, 'Notice', `Detected unsupported annotation '@deprecated'`, `This annotation is not currently supported by the parser.`);
305
+ }
306
+ // NOTE: Don't implement! Only experimental / testing for future.
307
+ return null;
308
+ };
309
+ /**
310
+ * Visit a parse tree produced by `YiniParser.eol`.
311
+ * @param ctx the parse tree
312
+ * @return the visitor result
313
+ */
314
+ // visitEol?: (ctx: EolContext) => Result
315
+ this.visitEol = (ctx) => null;
316
+ /**
317
+ * Visit a parse tree produced by `YiniParser.assignment`.
318
+ * @param ctx the parse tree
319
+ * @return the visitor result
320
+ */
321
+ // visitAssignment?: (ctx: AssignmentContext) => Result
322
+ this.visitAssignment = (ctx) => {
323
+ var _a;
324
+ (0, env_1.isDebug)() && console.log();
325
+ (0, print_1.debugPrint)('-> Entered visitAssignment(..)');
326
+ // assignment : member eol
327
+ const mem = ctx.member();
328
+ (_a = this.visitMember) === null || _a === void 0 ? void 0 : _a.call(this, mem);
329
+ return null;
330
+ };
331
+ /**
332
+ * Visit a parse tree produced by `YiniParser.member`.
333
+ * @param ctx the parse tree
334
+ * @grammarRule KEY WS? EQ WS? value?
335
+ * @return the visitor result
336
+ */
337
+ // visitMember?: (ctx: MemberContext) => Result
338
+ this.visitMember = (ctx) => {
339
+ var _a, _b, _c, _d, _e, _f;
340
+ (0, env_1.isDebug)() && console.log();
341
+ (0, print_1.debugPrint)('-> Entered visitMember(..)');
342
+ // member: KEY WS? EQ WS? value?
343
+ const rawKey = ctx.getChild(0).getText();
344
+ (0, print_1.debugPrint)(`visitMember(..): rawKey = '${rawKey}'`);
345
+ if (rawKey) {
346
+ (0, print_1.debugPrint)();
347
+ (0, print_1.debugPrint)('Has a key... Validate it either as a simple or a backticked ident...');
348
+ if ((0, string_1.isEnclosedInBackticks)(rawKey)) {
349
+ if (!(0, yiniHelpers_1.isValidBacktickedIdent)(rawKey)) {
350
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
351
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', `Invalid (backticked) key/identifier: '${rawKey}'`, 'Backticked key/identifier should be like e.g. `My section name`.');
352
+ }
353
+ }
354
+ else {
355
+ if (!(0, yiniHelpers_1.isValidSimpleIdent)(rawKey)) {
356
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
357
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', `Invalid key/identifier name: '${rawKey}'`, `Key/identifier names must start with: A-Z, a-z, or _, unless enclosed in backticks e.g.: \`${rawKey}\` or \`My key name\`.`);
358
+ }
359
+ }
360
+ }
361
+ const resultKey = (0, string_1.trimBackticks)(rawKey);
362
+ const rawValue = (_b = (_a = ctx.value) === null || _a === void 0 ? void 0 : _a.call(ctx)) === null || _b === void 0 ? void 0 : _b.getText();
363
+ (0, print_1.debugPrint)(`visitMember(..): rawValue = ` + ((_d = (_c = ctx.value) === null || _c === void 0 ? void 0 : _c.call(ctx)) === null || _d === void 0 ? void 0 : _d.getText()));
364
+ let valueContext = (_e = ctx.value) === null || _e === void 0 ? void 0 : _e.call(ctx);
365
+ let valueNode;
366
+ if (!rawValue) {
367
+ // Empty value => Null in lenient mode, error in strict (Spec 12.3, 8.2). :contentReference[oaicite:10]{index=10}:contentReference[oaicite:11]{index=11}
368
+ if (!this.isStrict) {
369
+ valueNode = makeScalarValue('Null', null, 'Implicit null');
370
+ }
371
+ else {
372
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
373
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', `Strict mode: missing value for key '${resultKey}'`, 'Expected a value but found nothing, strict mode does not allow implicit null.', "If you intend to have a null value, please specify 'null' explicitly as the value.");
374
+ }
375
+ }
376
+ else {
377
+ valueNode = (_f = this.visitValue) === null || _f === void 0 ? void 0 : _f.call(this, valueContext);
378
+ }
379
+ (0, print_1.debugPrint)('visitMember(..): valueNode:');
380
+ if ((0, env_1.isDebug)()) {
381
+ (0, print_1.printObject)(valueNode);
382
+ }
383
+ // const resultType = valueLiteral?.type
384
+ // const resultValue = valueLiteral?.type
385
+ if (!valueNode || valueNode.type === 'Undefined') {
386
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
387
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid value', `Invalid value for key '${resultKey} in member (<key> = <value> pair)'.`, `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`);
388
+ }
389
+ const current = this.sectionStack[this.sectionStack.length - 1];
390
+ if (valueNode !== undefined) {
391
+ this.putMember(this.errorHandler, ctx, current, resultKey, valueNode,
392
+ // this.ast,
393
+ this.onDuplicateKey);
394
+ }
395
+ return valueNode;
396
+ };
397
+ /**
398
+ * Visit a parse tree produced by `YiniParser.value`.
399
+ * @param ctx the parse tree
400
+ * @return the visitor result
401
+ */
402
+ // visitValue?: (ctx: ValueContext) => Result
403
+ this.visitValue = (ctx) => {
404
+ (0, print_1.debugPrint)('----------------------------');
405
+ (0, print_1.debugPrint)('-> Entered visitValue(..)');
406
+ let valueNode = undefined;
407
+ if (ctx.null_literal()) {
408
+ (0, print_1.debugPrint)(' visiting visitNull_literal(..)');
409
+ valueNode = this.visitNull_literal(ctx.null_literal());
410
+ }
411
+ else if (ctx.string_literal()) {
412
+ (0, print_1.debugPrint)(' visiting visitString_literal(..)');
413
+ valueNode = this.visitString_literal(ctx.string_literal());
414
+ }
415
+ else if (ctx.number_literal()) {
416
+ (0, print_1.debugPrint)(' visiting visitNumber_literal(..)');
417
+ valueNode = this.visitNumber_literal(ctx.number_literal());
418
+ }
419
+ else if (ctx.boolean_literal()) {
420
+ (0, print_1.debugPrint)(' visiting visitBoolean_literal(..)');
421
+ valueNode = this.visitBoolean_literal(ctx.boolean_literal());
422
+ }
423
+ else if (ctx.list_literal()) {
424
+ (0, print_1.debugPrint)(' visiting visitList_literal(..)');
425
+ valueNode = this.visitList_literal(ctx.list_literal());
426
+ }
427
+ else if (ctx.object_literal()) {
428
+ (0, print_1.debugPrint)(' visiting visitObject_literal(..)');
429
+ valueNode = this.visitObject_literal(ctx.object_literal());
430
+ }
431
+ else {
432
+ (0, print_1.debugPrint)(' Entered else case in visitValue(..)');
433
+ valueNode = makeScalarValue('Undefined', undefined, 'Invalid value');
434
+ }
435
+ (0, print_1.debugPrint)('<- About to exit visitValue(..): returning:');
436
+ if ((0, env_1.isDebug)()) {
437
+ (0, print_1.printObject)(valueNode);
438
+ }
439
+ (0, print_1.debugPrint)('----------------------------\n');
440
+ return valueNode;
441
+ };
442
+ /**
443
+ * Visit a parse tree produced by `YiniParser.string_literal`.
444
+ * @param ctx the parse tree
445
+ * @return the visitor result
446
+ */
447
+ // visitString_literal?: (ctx: String_literalContext) => Result
448
+ this.visitString_literal = (ctx) => {
449
+ var _a, _b;
450
+ (0, print_1.debugPrint)('-> Entered visitString_literal(..)');
451
+ // STRING (string_concat)*
452
+ // Concatenate pieces with PLUS (Spec 6.6). Each piece is a STRING token; '+' is structural. :contentReference[oaicite:17]{index=17}
453
+ let text = trimQuotes(ctx.STRING().getText());
454
+ // for (const c of ctx.string_concat() ?? []) {
455
+ for (const c of (_a = ctx.string_concat_list()) !== null && _a !== void 0 ? _a : []) {
456
+ (0, print_1.debugPrint)('c of ctx.string_concat():');
457
+ (0, env_1.isDebug)() && (0, print_1.printObject)(c);
458
+ text += (_b = this.visitString_concat) === null || _b === void 0 ? void 0 : _b.call(this, c);
459
+ }
460
+ return makeScalarValue('String', text);
461
+ };
462
+ /**
463
+ * Visit a parse tree produced by `YiniParser.number_literal`.
464
+ * @param ctx the parse tree
465
+ * @return the visitor result
466
+ */
467
+ // visitNumber_literal?: (ctx: Number_literalContext) => Result
468
+ this.visitNumber_literal = (ctx) => {
469
+ (0, print_1.debugPrint)('-> Entered visitNumber_literal(..)');
470
+ const rawText = ctx.getText();
471
+ const parsedNum = (0, parseNumber_1.default)(rawText);
472
+ (0, print_1.debugPrint)('parseNumberLiteral(..) returned, parsedNum:');
473
+ if ((0, env_1.isDebug)()) {
474
+ (0, print_1.printObject)(parsedNum);
475
+ }
476
+ // Check if number is JS special type NaN, Infinity or -Infinity.
477
+ // Note: Value with a zero (0) should pass OK!
478
+ if ((parsedNum === null || parsedNum === void 0 ? void 0 : parsedNum.value) !== 0 &&
479
+ (!(parsedNum === null || parsedNum === void 0 ? void 0 : parsedNum.value) ||
480
+ (0, number_1.isNaNValue)(parsedNum.value) ||
481
+ (0, number_1.isInfinityValue)(parsedNum.value))) {
482
+ // **************************************************
483
+ // NOTE: (!) Currently a bit unsure if to return
484
+ // option 1 or 2..!?, 2025-08-23
485
+ // Option 1.
486
+ // return makeScalarValue('Undefined', undefined, parsedNum?.tag)
487
+ // Option 2.
488
+ return undefined;
489
+ // **************************************************
490
+ }
491
+ const value = makeScalarValue('Number', parsedNum.value, parsedNum.tag);
492
+ if ((0, env_1.isDebug)()) {
493
+ console.log(' rawText = ' + rawText);
494
+ console.log('parsedNum = ' + parsedNum.value);
495
+ console.log('Number literal:');
496
+ (0, yiniHelpers_1.printLiteral)(value);
497
+ // return parseNumber(ctx.getText())
498
+ }
499
+ return value;
500
+ };
501
+ /**
502
+ * Visit a parse tree produced by `YiniParser.boolean_literal`.
503
+ * @param ctx the parse tree
504
+ * @return the visitor result
505
+ */
506
+ // visitBoolean_literal?: (ctx: Boolean_literalContext) => Result
507
+ this.visitBoolean_literal = (ctx) => {
508
+ (0, print_1.debugPrint)('-> Entered visitBoolean_literal(..)');
509
+ const raw = ctx.getText();
510
+ (0, print_1.debugPrint)('raw: "' + raw + '"');
511
+ // Case-insensitive true/false/on/off/yes/no (Spec section, 8.1).
512
+ return makeScalarValue('Boolean', (0, parseBoolean_1.default)(raw));
513
+ };
514
+ /**
515
+ * Visit a parse tree produced by `YiniParser.null_literal`.
516
+ * @param ctx the parse tree
517
+ * @return the visitor result
518
+ */
519
+ // visitNull_literal?: (ctx: Null_literalContext) => Result
520
+ this.visitNull_literal = (ctx) => {
521
+ (0, print_1.debugPrint)('-> Entered visitNull_literal(..)');
522
+ (0, print_1.debugPrint)('raw = ' + ctx.getText());
523
+ return makeScalarValue('Null', null, 'Explicit Null');
524
+ };
525
+ /**
526
+ * Visit a parse tree produced by `YiniParser.list_literal`.
527
+ * @param ctx the parse tree
528
+ * @grammarRule OB NL* elements? NL* CB NL* | EMPTY_LIST NL*
529
+ * @return the visitor result
530
+ */
531
+ // visitList_literal?: (ctx: List_literalContext) => Result
532
+ this.visitList_literal = (ctx) => {
533
+ (0, print_1.debugPrint)('-> Entered visitList_literal(..)');
534
+ // '[' elements? ']' ; empty_list handled by lexer (Spec section, 10.1). :contentReference[oaicite:14]{index=14}
535
+ const elems = this.visitElements(ctx.elements());
536
+ const value = makeListValue(elems, 'From bracketed list');
537
+ (0, print_1.debugPrint)('<- About to exit visitList_literal(..)...');
538
+ if ((0, env_1.isDebug)()) {
539
+ console.log('List literal:');
540
+ (0, print_1.printObject)(value);
541
+ }
542
+ return value;
543
+ };
544
+ /**
545
+ * Visit a parse tree produced by `YiniParser.elements`.
546
+ * @param ctx the parse tree
547
+ * @grammarRule value (NL* COMMA NL* value)* COMMA?
548
+ * @return the visitor result
549
+ */
550
+ this.visitElements = (ctx) => {
551
+ (0, print_1.debugPrint)('-> Entered visitElements(..)');
552
+ (0, print_1.debugPrint)(' elements.length = ' + (ctx === null || ctx === void 0 ? void 0 : ctx.value_list().length));
553
+ const elems = !(ctx === null || ctx === void 0 ? void 0 : ctx.value_list())
554
+ ? []
555
+ : ctx.value_list().map((elem) => {
556
+ const valueNode = this.visitValue(elem);
557
+ if (!valueNode) {
558
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
559
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid list element', `Invalid list element: '${elem === null || elem === void 0 ? void 0 : elem.getText()}'`, `Expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`);
560
+ }
561
+ return valueNode;
562
+ });
563
+ (0, print_1.debugPrint)('<- About to exit visitElements(..)');
564
+ if ((0, env_1.isDebug)()) {
565
+ console.log('Mapped value_list of elements in a list:');
566
+ (0, print_1.printObject)(elems);
567
+ }
568
+ return elems;
569
+ };
570
+ /**
571
+ * Visit a parse tree produced by `YiniParser.object_literal`.
572
+ * @param ctx the parse tree
573
+ * @grammarRule OC NL* object_members? NL* CC NL* | EMPTY_OBJECT NL*
574
+ * @return the visitor result
575
+ */
576
+ this.visitObject_literal = (ctx) => {
577
+ (0, print_1.debugPrint)('-> Entered visitObject_literal(..)');
578
+ // debugPrint('entries.EMPTY_OBJECT = ' + ctx?.EMPTY_OBJECT())
579
+ // debugPrint('entries.length = ' + ctx?.object_members())
580
+ // printObject(ctx)
581
+ // const entries = this.visitObject_members(ctx?.object_members())
582
+ const entries = ctx.object_members()
583
+ ? this.visitObject_members(ctx.object_members())
584
+ : {};
585
+ const value = makeObjectValue(entries);
586
+ (0, print_1.debugPrint)('<- About to exit visitObject_literal(..)...');
587
+ if ((0, env_1.isDebug)()) {
588
+ console.log('Object literal:');
589
+ (0, print_1.printObject)(value);
590
+ }
591
+ return value;
592
+ };
593
+ /**
594
+ * Visit a parse tree produced by `YiniParser.object_members`.
595
+ * @param ctx the parse tree
596
+ * @grammarRule object_member (COMMA NL* object_member)* COMMA?
597
+ * @return the visitor result
598
+ */
599
+ this.visitObject_members = (ctx) => {
600
+ (0, print_1.debugPrint)('-> Entered visitObject_members(..)');
601
+ (0, print_1.debugPrint)('entries.length = ' + (ctx === null || ctx === void 0 ? void 0 : ctx.object_member_list().length));
602
+ const entries = [];
603
+ ctx.object_member_list().forEach((member) => {
604
+ const { key, value } = this.visitObject_member(member);
605
+ (0, print_1.debugPrint)(' key = ' + key);
606
+ entries[key] = value;
607
+ });
608
+ (0, print_1.debugPrint)('<- About to exit visitObject_members(..)');
609
+ return entries;
610
+ };
611
+ /**
612
+ * Visit a parse tree produced by `YiniParser.object_member`.
613
+ * @param ctx the parse tree
614
+ * @grammarRule KEY WS? COLON NL* value
615
+ * @return the visitor result
616
+ */
617
+ // visitObject_member?: (ctx: Object_memberContext) => Result
618
+ this.visitObject_member = (ctx) => {
619
+ (0, print_1.debugPrint)('-> Entered visitObject_member(..)');
620
+ const rawKey = ctx.KEY().getText();
621
+ const key = (0, string_1.trimBackticks)(rawKey);
622
+ const rawValue = ctx.value().getText();
623
+ const valueNode = ctx.value()
624
+ ? this.visitValue(ctx.value())
625
+ : makeScalarValue('Null', 'Implicit Null');
626
+ (0, print_1.debugPrint)(' rawKey = ' + rawKey);
627
+ (0, print_1.debugPrint)(' key = ' + key);
628
+ (0, print_1.debugPrint)('rawValue = ' + rawValue);
629
+ if (!valueNode) {
630
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
631
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid object entry', `Invalid object entry for key '${key}'.`, `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`);
632
+ }
633
+ (0, print_1.debugPrint)('<- About to exit visitObject_member(..)');
634
+ if ((0, env_1.isDebug)()) {
635
+ console.log('Returning:');
636
+ (0, print_1.printObject)({ key, value: valueNode });
637
+ }
638
+ return { key, value: valueNode };
639
+ };
640
+ /**
641
+ * Visit a parse tree produced by `YiniParser.colon_list_decl`.
642
+ * @param ctx the parse tree
643
+ * @grammarRule KEY WS? COLON (eol | WS+)* elements (eol | WS+)* eol
644
+ * @return the visitor result
645
+ */
646
+ // visitColon_list_decl?: (ctx: ListAfterColonContext) => Result
647
+ this.visitColon_list_decl = (ctx) => {
648
+ (0, print_1.debugPrint)('-> Entered visitColon_list_decl(..)');
649
+ const key = ctx.getChild(0).getText();
650
+ (0, print_1.debugPrint)(`visitColon_list_decl(..): key = '${key}'`);
651
+ const elems = this.visitElements(ctx.elements());
652
+ const value = makeListValue(elems, 'From colon-list');
653
+ const current = this.sectionStack[this.sectionStack.length - 1];
654
+ // putMember(current, key, list, this.ast, this.onDuplicateKey)
655
+ this.putMember(this.errorHandler, ctx, current, key, value,
656
+ // this.ast,
657
+ this.onDuplicateKey);
658
+ (0, print_1.debugPrint)('<- About to exit visitColon_list_decl(..)...');
659
+ if ((0, env_1.isDebug)()) {
660
+ console.log('List literal: (from a Colon-list)');
661
+ (0, print_1.printObject)(value);
662
+ }
663
+ return value;
664
+ };
665
+ /**
666
+ * Visit a parse tree produced by `YiniParser.string_concat`.
667
+ * @param ctx the parse tree
668
+ * @return the visitor result
669
+ */
670
+ // visitString_concat?: (ctx: String_concatContext) => Result
671
+ this.visitString_concat = (ctx) => {
672
+ // PLUS STRING
673
+ return trimQuotes(ctx.STRING().getText());
674
+ };
675
+ /**
676
+ * Visit a parse tree produced by `YiniParser.bad_member`.
677
+ * @param ctx the parse tree
678
+ * @return the visitor result
679
+ */
680
+ // visitBad_member?: (ctx: Bad_memberContext) => Result
681
+ this.visitBad_member = (ctx) => {
682
+ var _a;
683
+ (0, print_1.debugPrint)('-> Entered visitBad_member(..)');
684
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
685
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid or malformed member (key-value pair) found.', `Offending text: ${(_a = ctx === null || ctx === void 0 ? void 0 : ctx.getText()) === null || _a === void 0 ? void 0 : _a.trim()}`, 'Members has the form: <key> = <value>, where <key> is a name/identifier and <value> is a value/literal.');
686
+ return null;
687
+ };
688
+ /**
689
+ * Visit a parse tree produced by `YiniParser.bad_meta_text`.
690
+ * @param ctx the parse tree
691
+ * @return the visitor result
692
+ */
693
+ this.visitBad_meta_text = (ctx) => {
694
+ var _a;
695
+ (0, print_1.debugPrint)('-> Entered visitBad_meta_text(..)');
696
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
697
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Invalid or malformed directive or annotation statement', `Offending statement: ${(_a = ctx === null || ctx === void 0 ? void 0 : ctx.getText()) === null || _a === void 0 ? void 0 : _a.trim()}`);
698
+ return null;
699
+ };
700
+ if (!errorHandler) {
701
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
702
+ new ErrorDataHandler_1.ErrorDataHandler('None/Ignore').pushOrBail(null, 'Fatal-Error', 'Has no ErrorDataHandler instance when calling visitYini(..)', 'Something in the code is done incorrectly in order for this to happen... :S');
703
+ }
704
+ _sourceType = sourceType;
705
+ this.options = options;
706
+ this.errorHandler = errorHandler;
707
+ this.isStrict = options.isStrict;
708
+ if (options.isStrict) {
709
+ this.onDuplicateKey = 'error';
710
+ }
711
+ else {
712
+ this.onDuplicateKey = 'warn';
713
+ }
714
+ const root = makeSection('(root)', 0);
715
+ // this.mapSectionNamePaths.set('(root)', 0)
716
+ this.ast = {
717
+ root,
718
+ isStrict: this.isStrict,
719
+ sourceType: metaFileName ? 'File' : 'Inline',
720
+ fileName: !!metaFileName ? metaFileName : undefined,
721
+ terminatorSeen: false,
722
+ yiniMarkerSeen: false,
723
+ maxDepth: null,
724
+ numOfSections: 0,
725
+ numOfMembers: 0,
726
+ sectionNamePaths: null,
727
+ };
728
+ this.sectionStack = [root];
729
+ }
730
+ /** Attach a section to the stack respecting up/down moves (Spec 5.3). :contentReference[oaicite:7]{index=7} */
731
+ attachSection(ctx, stack, section, ast) {
732
+ const targetLevel = section.level;
733
+ const sectionName = section.sectionName;
734
+ if (targetLevel <= 0) {
735
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
736
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Warning', `Invalid section level: ${targetLevel}`);
737
+ return;
738
+ }
739
+ // ------------------------------
740
+ // Construct section name path.
741
+ let keyPath = '';
742
+ let i = targetLevel - 1;
743
+ for (const [key, value] of Array.from(this.mapSectionNamePaths.entries()).reverse()) {
744
+ if (value === i) {
745
+ keyPath += key + '.';
746
+ break;
747
+ }
748
+ }
749
+ keyPath += sectionName; // Append current section name last.
750
+ (0, print_1.debugPrint)('section full path: keyPath = ' + keyPath);
751
+ // ------------------------------
752
+ if (this.hasDefinedSectionTitle(keyPath)) {
753
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
754
+ this.errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Duplicate section name', `Section name: '${sectionName}' at level ${targetLevel} is already defined and cannot be redefined.`);
755
+ }
756
+ else {
757
+ if (section.members === undefined) {
758
+ (0, print_1.debugPrint)('This sReslult does not hold any valid members (=undefined)');
759
+ }
760
+ else {
761
+ // this.existingSectionTitles.set(key, true)
762
+ this.setDefineSectionTitle(keyPath, targetLevel);
763
+ // printObject(this.existingSectionTitles)
764
+ }
765
+ }
766
+ // ------------------------------
767
+ // Ensure stack top has level targetLevel-1 (implicit root is level 0).
768
+ while (stack.length > 0 &&
769
+ stack[stack.length - 1].level >= targetLevel) {
770
+ stack.pop();
771
+ }
772
+ const parent = stack[stack.length - 1]; // root or higher-level section
773
+ parent.children.push(section);
774
+ stack.push(section);
775
+ if (targetLevel > this.meta_maxLevel) {
776
+ this.meta_maxLevel = targetLevel;
777
+ }
778
+ }
779
+ /** Insert a key/value into current section (duplicate handling per options). */
780
+ putMember(errorHandler, ctx, sec, key, value, mode = 'warn') {
781
+ (0, env_1.isDebug)() && console.log();
782
+ (0, print_1.debugPrint)('-> Entered putMember(..)');
783
+ (0, print_1.debugPrint)(`putMember(..): key: '${key}', value: ${value}`);
784
+ if (sec.members.has(key)) {
785
+ switch (mode) {
786
+ case 'error':
787
+ errorHandler.pushOrBail(ctx, 'Syntax-Error', 'Hit a duplicate key in this section and scope', `Key '${key}' already exists in section '${sec.sectionName}' on level ${sec.level}.`);
788
+ break;
789
+ case 'warn':
790
+ errorHandler.pushOrBail(ctx, 'Syntax-Warning', 'Hit a duplicate key (keeping first) in this section and scope', `Key '${key}' already exists in section '${sec.sectionName}' on level ${sec.level}.`);
791
+ return; // keep first
792
+ case 'keep-first':
793
+ return;
794
+ case 'overwrite':
795
+ // replace value
796
+ break;
797
+ }
798
+ }
799
+ sec.members.set(key, value);
800
+ this._numOfMembers++;
801
+ }
802
+ // --------------------------------
803
+ // Public entry
804
+ buildAST(ctx) {
805
+ var _a;
806
+ (_a = this.visitYini) === null || _a === void 0 ? void 0 : _a.call(this, ctx);
807
+ // The document terminator is optional by default.
808
+ // If the option `isRequireDocTerminator` is set to true,
809
+ // the '/END' terminator at the end of the document becomes required.
810
+ if (this.options.isRequireDocTerminator && !this.ast.terminatorSeen) {
811
+ const msgWhat = `Missing '/END' at end of document (option 'isRequireDocTerminator' is enabled).`;
812
+ const msgWhy = `The terminator '/END' (case insensitive) is required and must appear at the end of the document.`;
813
+ // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
814
+ this.errorHandler.pushOrBail(null, 'Syntax-Error', msgWhat, msgWhy);
815
+ }
816
+ // Note: Below is important for error checking as well as for meta data.
817
+ this.ast.numOfSections = this.mapSectionNamePaths.size;
818
+ this.ast.numOfMembers = this._numOfMembers;
819
+ if (this.options.isIncludeMeta) {
820
+ // Attach collected meta information.
821
+ this.ast.maxDepth = this.meta_maxLevel;
822
+ this.ast.sectionNamePaths = [...this.mapSectionNamePaths.keys()];
823
+ }
824
+ return this.ast;
825
+ }
826
+ }
827
+ exports.default = ASTBuilder;