yini-parser 1.0.2-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.
- package/CHANGELOG.md +28 -0
- package/README.md +83 -98
- package/dist/YINI.d.ts +34 -11
- package/dist/YINI.js +206 -93
- package/dist/core/ASTBuilder.d.ts +191 -0
- package/dist/core/ASTBuilder.js +827 -0
- package/dist/core/ErrorDataHandler.d.ts +19 -19
- package/dist/core/ErrorDataHandler.js +258 -150
- package/dist/core/objectBuilder.d.ts +9 -3
- package/dist/core/objectBuilder.js +126 -163
- package/dist/core/types.d.ts +234 -44
- package/dist/core/types.js +7 -33
- package/dist/grammar/YiniLexer.d.ts +54 -48
- package/dist/grammar/YiniLexer.js +324 -289
- package/dist/grammar/YiniParser.d.ts +167 -150
- package/dist/grammar/YiniParser.js +1241 -1202
- package/dist/grammar/YiniParserVisitor.d.ts +59 -45
- package/dist/grammar/YiniParserVisitor.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +286 -26
- package/dist/parseEntry.d.ts +3 -2
- package/dist/parseEntry.js +352 -81
- package/dist/parsers/extractHeaderParts.d.ts +3 -2
- package/dist/parsers/extractHeaderParts.js +1 -0
- package/dist/parsers/parseBoolean.d.ts +1 -1
- package/dist/parsers/parseBoolean.js +2 -1
- package/dist/parsers/parseNumber.d.ts +8 -3
- package/dist/parsers/parseNumber.js +21 -7
- package/dist/parsers/parseSectionHeader.d.ts +3 -2
- package/dist/parsers/parseSectionHeader.js +1 -0
- package/dist/utils/number.d.ts +3 -0
- package/dist/utils/number.js +18 -0
- package/dist/utils/object.d.ts +55 -0
- package/dist/utils/object.js +85 -0
- package/dist/utils/string.d.ts +21 -1
- package/dist/utils/string.js +39 -4
- package/dist/utils/system.d.ts +15 -0
- package/dist/utils/system.js +21 -0
- package/dist/yiniHelpers.d.ts +3 -0
- package/dist/yiniHelpers.js +43 -7
- package/package.json +1 -1
- package/dist/core/YINIVisitor.d.ts +0 -158
- package/dist/core/YINIVisitor.js +0 -1008
|
@@ -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;
|