yini-parser 1.4.3 → 1.5.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/CHANGELOG.md +19 -0
- package/README.md +18 -12
- package/dist/YINI.d.ts +9 -13
- package/dist/YINI.js +3 -3
- package/dist/YINI.js.map +1 -1
- package/dist/core/astBuilder.d.ts +3 -6
- package/dist/core/astBuilder.js +80 -49
- package/dist/core/astBuilder.js.map +1 -1
- package/dist/core/objectBuilder.d.ts +8 -4
- package/dist/core/objectBuilder.js +31 -61
- package/dist/core/objectBuilder.js.map +1 -1
- package/dist/core/options/defaultParserOptions.js +9 -1
- package/dist/core/options/defaultParserOptions.js.map +1 -1
- package/dist/core/options/optionsFunctions.js +1 -3
- package/dist/core/options/optionsFunctions.js.map +1 -1
- package/dist/core/parsingRules/modeFromRulesMatcher.d.ts +1 -1
- package/dist/core/parsingRules/modeFromRulesMatcher.js +22 -13
- package/dist/core/parsingRules/modeFromRulesMatcher.js.map +1 -1
- package/dist/core/pipeline/pipeline.js +4 -0
- package/dist/core/pipeline/pipeline.js.map +1 -1
- package/dist/dev/main.js +55 -84
- package/dist/dev/main.js.map +1 -1
- package/dist/grammar/generated/YiniLexer.d.ts +43 -49
- package/dist/grammar/generated/YiniLexer.js +320 -332
- package/dist/grammar/generated/YiniLexer.js.map +1 -1
- package/dist/grammar/generated/YiniParser.d.ts +60 -82
- package/dist/grammar/generated/YiniParser.js +461 -726
- package/dist/grammar/generated/YiniParser.js.map +1 -1
- package/dist/grammar/generated/YiniParserVisitor.d.ts +7 -7
- package/dist/grammar/generated/YiniParserVisitor.js +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.5.0 - 2026 Apr
|
|
4
|
+
- **Updated:** Parser behavior aligned with YINI Specification `v1.0.0-RC.5`.
|
|
5
|
+
- **Changed:** In strict mode, YINI documents must now end with the document terminator `/END`.
|
|
6
|
+
- **Changed:** Strict mode now requires exactly one explicit top-level section.
|
|
7
|
+
- **Updated:** Section header parsing now reflects the latest horizontal whitespace (`HSPACE`) rules from the specification.
|
|
8
|
+
- **Improved:** Refined how members outside explicit sections are handled in lenient mode, and added stricter checks for top-level structure in strict mode.
|
|
9
|
+
- **Improved:** Clarified and tightened how empty values and explicit `null` are handled in lenient and strict mode.
|
|
10
|
+
- **Improved:** Fixed `throwOnError` option detection, clarified the related documentation, and cleaned up public parameter names.
|
|
11
|
+
- **Fixed:** Made `throwOnError` work consistently together with the selected fail level.
|
|
12
|
+
- **Updated:** Synced to the latest lexer and parser grammar files from the Specification Package `v1.0.0-RC.5`, with cleaned up and refined grammar files for better consistency and maintainability.
|
|
13
|
+
- **Added:** Expanded validation and tests for section headers, including shorthand markers, backticked names, and invalid dotted section names.
|
|
14
|
+
- **Expanded:** Improved integration and smoke test coverage for error recovery, throw behavior, null handling, fixture parsing, and section/header edge cases.
|
|
15
|
+
- **Updated:** Added and validated a new large smoke/golden fixture:
|
|
16
|
+
- `tests/fixtures/smoke-fixtures/c-industrial-monitoring-and-automation-platform.smoke.yini`
|
|
17
|
+
- **Improved:** Fixed and re-enabled previously skipped smoke tests:
|
|
18
|
+
- `tests/fixtures/smoke-fixtures/8-api-keys-integration.smoke.yini`
|
|
19
|
+
- `tests/fixtures/smoke-fixtures/9-app-preferences.smoke.yini`
|
|
20
|
+
|
|
3
21
|
## 1.4.3 - 2026 Apr
|
|
22
|
+
- **Promoted:** YINI Parser TypeScript is now considered stable (non-beta) after iterative beta releases and refinements.
|
|
4
23
|
- **Fixed:** Rebuilt the project and reduced reported vulnerabilities from 4 to 0.
|
|
5
24
|
|
|
6
25
|
## 1.4.3-beta - 2026 Mar
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# yini-parser
|
|
2
|
-
> **Readable configuration for Node.js and TypeScript — without YAML
|
|
2
|
+
> **Readable configuration for Node.js and TypeScript/JavaScript — without YAML footguns or JSON noise.**
|
|
3
3
|
|
|
4
|
-
The official TypeScript / Node.js parser for **YINI** (by the YINI-lang project) — a human-friendly configuration format with
|
|
4
|
+
The official TypeScript / Node.js parser for **YINI** (by the YINI-lang project) — a human-friendly configuration format with clear structure, nested sections, comments, and predictable parsing.
|
|
5
5
|
|
|
6
6
|
YINI is designed for applications, tools, and services that need configuration that stays readable for humans without becoming vague, fragile, or hard to maintain.
|
|
7
7
|
|
|
8
|
-
[](https://www.npmjs.com/package/yini-parser) [](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml) [](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml) [](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml) [](https://www.npmjs.com/package/yini-parser)
|
|
8
|
+
[](https://www.npmjs.com/package/yini-parser) [](https://www.typescriptlang.org/) [](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml) [](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml) [](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml) [](https://www.npmjs.com/package/yini-parser)
|
|
9
9
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
@@ -36,18 +36,18 @@ console.log(config.App.Features.caching) // true
|
|
|
36
36
|
## 🙋♀️ Why try YINI?
|
|
37
37
|
|
|
38
38
|
- **Readable by humans** — Less noisy than JSON, less fragile than indentation-driven formats.
|
|
39
|
-
- **Structured
|
|
39
|
+
- **Structured for real-world configuration** — Sections, nested sections, lists, objects, booleans, and null.
|
|
40
40
|
- **Predictable parsing** — Explicit syntax with clear rules.
|
|
41
41
|
- **Easy to use from TypeScript/Node.js** — Parse from strings or files in a few lines.
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
45
|
## What YINI looks like in practice
|
|
46
|
-
> A basic YINI configuration example, showing a section, nested section, comments:
|
|
46
|
+
> A basic YINI configuration example, showing a section, a nested section, and comments:
|
|
47
47
|

|
|
48
48
|
Source: [basic.yini](./samples/basic.yini)
|
|
49
49
|
|
|
50
|
-
- ▶️
|
|
50
|
+
- ▶️ [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete usage examples.
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
@@ -55,7 +55,7 @@ Source: [basic.yini](./samples/basic.yini)
|
|
|
55
55
|
- **Indentation-independent structure:** Spaces and tabs never change meaning, so files can be reformatted without changing structure.
|
|
56
56
|
- **Explicit nesting:** Hierarchy is defined with section markers like `^`, `^^`, and `^^^`, making large configurations easier to scan and refactor.
|
|
57
57
|
- **Multiple data types:** Supports booleans (`true` / `false`, `yes` / `no`, etc.), numbers, lists, and inline objects, with explicit string syntax.
|
|
58
|
-
- **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`), making it
|
|
58
|
+
- **Comment support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`), making it easy to document configuration directly in the file.
|
|
59
59
|
- **Predictable parsing:** Clear rules with optional strict and lenient modes for different use cases.
|
|
60
60
|
|
|
61
61
|
---
|
|
@@ -141,7 +141,7 @@ Source: [config.yini](./samples/config.yini)
|
|
|
141
141
|
|
|
142
142
|
## 🧪 Testing and Stability
|
|
143
143
|
|
|
144
|
-
This parser is
|
|
144
|
+
This parser is validated through regression and smoke tests to help ensure stable, predictable parsing across default, strict, and metadata-enabled modes.
|
|
145
145
|
|
|
146
146
|
---
|
|
147
147
|
|
|
@@ -149,16 +149,22 @@ This parser is continuously validated through comprehensive regression and smoke
|
|
|
149
149
|
- ➡️ [YINI Homepage](https://yini-lang.org)
|
|
150
150
|
*Tutorials, guides, and examples.*
|
|
151
151
|
|
|
152
|
+
- ➡️ [Read the YINI Specification](https://yini-lang.org/refs/specification)
|
|
153
|
+
*Full syntax and format reference.*
|
|
154
|
+
|
|
152
155
|
- ➡️ [YINI CLI on GitHub](https://github.com/YINI-lang/yini-cli)
|
|
153
156
|
*CLI tooling for working with YINI files.*
|
|
154
157
|
|
|
155
|
-
- ➡️ [
|
|
158
|
+
- ➡️ [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main)
|
|
159
|
+
*Complete basic usage examples.*
|
|
160
|
+
|
|
161
|
+
- ➡️ [YINI-lang Project](https://github.com/YINI-lang)
|
|
156
162
|
*Repositories and related ecosystem projects.*
|
|
157
163
|
|
|
158
164
|
---
|
|
159
165
|
|
|
160
166
|
## 🤝 Contributing
|
|
161
|
-
|
|
167
|
+
Feedback, bug reports, feature requests, and code contributions are welcome.
|
|
162
168
|
- [Open an Issue](https://github.com/YINI-lang/yini-parser-typescript/issues)
|
|
163
169
|
- [Start a Discussion](https://github.com/YINI-lang/yini-parser-typescript/discussions)
|
|
164
170
|
|
|
@@ -171,9 +177,9 @@ If this library is useful to you, a GitHub star helps more people discover the p
|
|
|
171
177
|
---
|
|
172
178
|
|
|
173
179
|
## License
|
|
174
|
-
This project is licensed under the Apache
|
|
180
|
+
This project is licensed under the Apache License 2.0 — see the [LICENSE](./LICENSE) file for details.
|
|
175
181
|
|
|
176
|
-
In this project on GitHub, the `libs` directory contains third
|
|
182
|
+
In this project on GitHub, the `libs` directory contains third-party software and each is licensed under its own license, described in its own license file under the respective directory under `libs`.
|
|
177
183
|
|
|
178
184
|
---
|
|
179
185
|
|
package/dist/YINI.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ParsedObject, ParseOptions, PreferredFailLevel, YiniParseResult } from './types';
|
|
2
2
|
/**
|
|
3
|
-
* This class is the public API
|
|
4
|
-
*
|
|
5
|
-
* @note
|
|
3
|
+
* This class is the main public API. It exposes `parse(..)` and `parseFile(..)`
|
|
4
|
+
* as the primary entry points, while the implementation details remain internal.
|
|
5
|
+
* @note The public parsing API is exposed through `parse(..)` and `parseFile(..)`.
|
|
6
6
|
*/
|
|
7
7
|
export default class YINI {
|
|
8
8
|
private static g_tabSize;
|
|
@@ -73,16 +73,14 @@ export default class YINI {
|
|
|
73
73
|
* Does not affect warnings included in returned metadata.
|
|
74
74
|
* @param options.requireDocTerminator - Controls whether a document terminator is required.
|
|
75
75
|
* Allowed values: `'optional'` | `'warn-if-missing'` | `'required'`.
|
|
76
|
-
* @param options.silent - Suppress all output
|
|
76
|
+
* @param options.silent - Suppress all console output, including errors and warnings.
|
|
77
77
|
* @param options.strictMode - Sets the baseline ruleset (true = strict, false = lenient).
|
|
78
78
|
* This is only a starting point: rule-specific options (e.g., `treatEmptyValueAsNull`,
|
|
79
79
|
* `onDuplicateKey`, etc.) can override parts of that ruleset. If any overrides are given,
|
|
80
80
|
* the effective mode becomes **custom** rather than purely strict/lenient.
|
|
81
81
|
* @param options.treatEmptyValueAsNull - How to treat an explicitly empty value on the
|
|
82
82
|
* right-hand side of '='. Allowed values: `'allow'` | `'allow-with-warning'` | `'disallow'`.
|
|
83
|
-
* @param options.throwOnError -
|
|
84
|
-
* NOTE: Current default is `true`. The default will change to `false` in the next
|
|
85
|
-
* release. To avoid breaking changes, set this option explicitly.
|
|
83
|
+
* @param options.throwOnError - Throw when a parse issue reaches the active bail threshold (for example, on errors if `failLevel = 'errors'`).
|
|
86
84
|
*
|
|
87
85
|
* @returns {ParsedObject | YiniParseResult} The parsed YINI content.
|
|
88
86
|
*
|
|
@@ -106,7 +104,7 @@ export default class YINI {
|
|
|
106
104
|
/**
|
|
107
105
|
* Parse a YINI file into a JavaScript object.
|
|
108
106
|
*
|
|
109
|
-
* @param
|
|
107
|
+
* @param filePath Path to the YINI file.
|
|
110
108
|
* @param strictMode If `true`, enforce strict parsing rules (e.g. require `/END`, disallow trailing commas).
|
|
111
109
|
* @param failLevel Preferred bail sensitivity level, controls if and when parsing should stop on problems:
|
|
112
110
|
* - `'auto'` (default) : Auto‑select level (strict → `'errors'`, lenient → `'ignore-errors'`)
|
|
@@ -137,7 +135,7 @@ export default class YINI {
|
|
|
137
135
|
/**
|
|
138
136
|
* Parse a YINI file into a JavaScript object, using an options object for configuration.
|
|
139
137
|
*
|
|
140
|
-
* @param
|
|
138
|
+
* @param filePath Path to the YINI file.
|
|
141
139
|
* @param options Optional settings to customize parsing and/or results, useful if you need more control.
|
|
142
140
|
* For all options, see types/ParseOptions.
|
|
143
141
|
*
|
|
@@ -161,16 +159,14 @@ export default class YINI {
|
|
|
161
159
|
* Does not affect warnings included in returned metadata.
|
|
162
160
|
* @param options.requireDocTerminator - Controls whether a document terminator is required.
|
|
163
161
|
* Allowed values: `'optional'` | `'warn-if-missing'` | `'required'`.
|
|
164
|
-
* @param options.silent - Suppress all output
|
|
162
|
+
* @param options.silent - Suppress all console output, including errors and warnings.
|
|
165
163
|
* @param options.strictMode - Sets the baseline ruleset (true = strict, false = lenient).
|
|
166
164
|
* This is only a starting point: rule-specific options (e.g., `treatEmptyValueAsNull`,
|
|
167
165
|
* `onDuplicateKey`, etc.) can override parts of that ruleset. If any overrides are given,
|
|
168
166
|
* the effective mode becomes **custom** rather than purely strict/lenient.
|
|
169
167
|
* @param options.treatEmptyValueAsNull - How to treat an explicitly empty value on the
|
|
170
168
|
* right-hand side of '='. Allowed values: `'allow'` | `'allow-with-warning'` | `'disallow'`.
|
|
171
|
-
* @param options.throwOnError -
|
|
172
|
-
* NOTE: Current default is `true`. The default will change to `false` in the next
|
|
173
|
-
* release. To avoid breaking changes, set this option explicitly.
|
|
169
|
+
* @param options.throwOnError - Throw when a parse issue reaches the active bail threshold (for example, on errors if `failLevel = 'errors'`).
|
|
174
170
|
*
|
|
175
171
|
* @returns {ParsedObject | YiniParseResult} The parsed YINI content.
|
|
176
172
|
*
|
package/dist/YINI.js
CHANGED
|
@@ -7,9 +7,9 @@ const runtime_1 = require("./core/runtime");
|
|
|
7
7
|
const print_1 = require("./utils/print");
|
|
8
8
|
const DEFAULT_TAB_SIZE = 4; // De facto "modern default" (even though traditionally/historically it's 8).
|
|
9
9
|
/**
|
|
10
|
-
* This class is the public API
|
|
11
|
-
*
|
|
12
|
-
* @note
|
|
10
|
+
* This class is the main public API. It exposes `parse(..)` and `parseFile(..)`
|
|
11
|
+
* as the primary entry points, while the implementation details remain internal.
|
|
12
|
+
* @note The public parsing API is exposed through `parse(..)` and `parseFile(..)`.
|
|
13
13
|
*/
|
|
14
14
|
class YINI {
|
|
15
15
|
/**
|
package/dist/YINI.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"YINI.js","sourceRoot":"","sources":["../src/YINI.ts"],"names":[],"mappings":";;AAAA,sCAA6C;AAC7C,8DAA0D;AAC1D,sEAAqE;AACrE,4CAA4C;AAO5C,yCAAiE;AAEjE,MAAM,gBAAgB,GAAG,CAAC,CAAA,CAAC,6EAA6E;AAExG;;;;GAIG;AACH,MAAqB,IAAI;IAIrB;;OAEG;IACI,MAAM,CAAC,UAAU;QACpB,OAAO,IAAI,CAAC,SAAS,CAAA;IACzB,CAAC;IAED;;;OAGG;IACI,MAAM,CAAC,UAAU,CAAC,MAAc;QACnC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,EAAE,EAAE,CAAC;YAC5B,IAAI,mCAAgB,CAAC,aAAa,CAAC,CAAC,UAAU,CAC1C,SAAS,EACT,aAAa,EACb,oBAAoB,MAAM,mBAAmB,EAC7C,2CAA2C,CAC9C,CAAA;YACD,MAAM,IAAI,UAAU,CAAC,YAAY,MAAM,0BAA0B,CAAC,CAAA;QACtE,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,MAAM,CAAA;IAC3B,CAAC;
|
|
1
|
+
{"version":3,"file":"YINI.js","sourceRoot":"","sources":["../src/YINI.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnnotationContext, AssignmentContext, Bad_memberContext, Bad_meta_textContext, Boolean_literalContext,
|
|
1
|
+
import { AnnotationContext, AssignmentContext, Bad_memberContext, Bad_meta_textContext, Boolean_literalContext, DirectiveContext, ElementsContext, EolContext, List_literalContext, MemberContext, Meta_stmtContext, Null_literalContext, Number_literalContext, Object_literalContext, Object_memberContext, Object_membersContext, PrologContext, StmtContext, String_concatContext, String_literalContext, Terminal_stmtContext, ValueContext, YiniContext } from '../grammar/generated/YiniParser.js';
|
|
2
2
|
import YiniParserVisitor from '../grammar/generated/YiniParserVisitor';
|
|
3
3
|
import { ErrorDataHandler } from './errorDataHandler';
|
|
4
4
|
import { IParseCoreOptions, IYiniAST, TSourceType } from './internalTypes';
|
|
@@ -28,6 +28,7 @@ export default class ASTBuilder<Result> extends YiniParserVisitor<Result> {
|
|
|
28
28
|
* @param metaLineCount Provide the line-count here so the meta information can be updated accordingly.
|
|
29
29
|
*/
|
|
30
30
|
constructor(errorHandler: ErrorDataHandler, options: IParseCoreOptions, sourceType: TSourceType, metaFileName: string | null);
|
|
31
|
+
private validateStrictTopLevelStructure;
|
|
31
32
|
private hasDefinedSectionTitle;
|
|
32
33
|
private setDefineSectionTitle;
|
|
33
34
|
private extractStringParts;
|
|
@@ -166,12 +167,8 @@ export default class ASTBuilder<Result> extends YiniParserVisitor<Result> {
|
|
|
166
167
|
*/
|
|
167
168
|
visitObject_member: (ctx: Object_memberContext) => any;
|
|
168
169
|
/**
|
|
169
|
-
*
|
|
170
|
-
* @param ctx the parse tree
|
|
171
|
-
* @grammarRule KEY WS? COLON (eol | WS+)* elements (eol | WS+)* eol
|
|
172
|
-
* @return the visitor result
|
|
170
|
+
* @note Colon list not supported any more since YINI Spec Package v1.0.0.rc4
|
|
173
171
|
*/
|
|
174
|
-
visitColon_list_decl: (ctx: Colon_list_declContext) => any;
|
|
175
172
|
/**
|
|
176
173
|
* Visit a parse tree produced by `YiniParser.string_concat`.
|
|
177
174
|
* @param ctx the parse tree
|
package/dist/core/astBuilder.js
CHANGED
|
@@ -40,6 +40,7 @@ const env_1 = require("../config/env");
|
|
|
40
40
|
const YiniParserVisitor_1 = __importDefault(require("../grammar/generated/YiniParserVisitor"));
|
|
41
41
|
const extractSignificantYiniLine_1 = require("../parsers/extractSignificantYiniLine");
|
|
42
42
|
const parseBoolean_1 = __importDefault(require("../parsers/parseBoolean"));
|
|
43
|
+
const parseNull_1 = __importDefault(require("../parsers/parseNull"));
|
|
43
44
|
const parseNumber_1 = __importDefault(require("../parsers/parseNumber"));
|
|
44
45
|
// import parseNumber from '../parsers/parseNumber'
|
|
45
46
|
const parseSectionHeader_1 = __importDefault(require("../parsers/parseSectionHeader"));
|
|
@@ -71,6 +72,7 @@ const makeScalarValue = (type, value = null, tag = undefined) => {
|
|
|
71
72
|
case 'Undefined':
|
|
72
73
|
return { type: 'Undefined', value: undefined, tag };
|
|
73
74
|
default:
|
|
75
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
74
76
|
new errorDataHandler_1.ErrorDataHandler(_sourceType).pushOrBail(undefined, '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');
|
|
75
77
|
}
|
|
76
78
|
return { type: 'Null', value: null, tag };
|
|
@@ -162,7 +164,6 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
162
164
|
// private meta_numOfChains = 0 // For stats.
|
|
163
165
|
this.meta_maxLevel = 0; // For stats.
|
|
164
166
|
this.mapSectionNamePaths = new Map();
|
|
165
|
-
// --- Private helper methods --------------------------------
|
|
166
167
|
this.hasDefinedSectionTitle = (keyPath) => {
|
|
167
168
|
return this.mapSectionNamePaths?.has(keyPath);
|
|
168
169
|
};
|
|
@@ -229,17 +230,18 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
229
230
|
this.visitStmt = (ctx) => {
|
|
230
231
|
const child = ctx.getChild(0);
|
|
231
232
|
const ruleName = child?.constructor?.name ?? '';
|
|
233
|
+
// debugPrint('S0')
|
|
234
|
+
// const badHeaderWDotName = ctx.BAD_SECTION_HEAD_W_DOT_NAME()?.getText()
|
|
235
|
+
// if (badHeaderWDotName) {
|
|
236
|
+
// console.log('QQQQQQQQ = ' + badHeaderWDotName)
|
|
237
|
+
// }
|
|
232
238
|
if (ruleName.includes('EolContext'))
|
|
233
239
|
return this.visitEol?.(child);
|
|
234
240
|
if (ruleName.includes('AssignmentContext'))
|
|
235
241
|
return this.visitAssignment?.(child);
|
|
236
|
-
if (ruleName.includes('Colon_list_declContext'))
|
|
237
|
-
return this.visitColon_list_decl?.(child);
|
|
238
242
|
if (ruleName.includes('Meta_stmtContext'))
|
|
239
243
|
return this.visitMeta_stmt?.(child);
|
|
240
244
|
(0, print_1.debugPrint)('S1');
|
|
241
|
-
// let headerAlt = child.getText?.() ?? ''
|
|
242
|
-
// let header = ctx.SECTION_HEAD()?.getText().trim() || ''
|
|
243
245
|
let header = ctx.SECTION_HEAD()?.getText().trim() || '';
|
|
244
246
|
// debugPrint('S2, lineAlt: >>>' + lineAlt + '<<<')
|
|
245
247
|
(0, print_1.debugPrint)('S2, header: >>>' + header + '<<<');
|
|
@@ -423,9 +425,11 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
423
425
|
break;
|
|
424
426
|
case 'allow-with-warning':
|
|
425
427
|
valueNode = makeScalarValue('Null', null, 'Implicit null (empty value)');
|
|
428
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
426
429
|
this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Warning', `Empty value treated as null for key '${resultKey}'.`, `An empty value after '=' was encountered. Per 'treatEmptyValueAsNull = allow-with-warning', interpreted as null.`, `If you intended null, write it explicitly: ${resultKey} = null. Otherwise provide a non-empty value or set 'treatEmptyValueAsNull' to 'disallow'.`);
|
|
427
430
|
break;
|
|
428
431
|
case 'disallow':
|
|
432
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
429
433
|
this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', `Missing value for key '${resultKey}'`, `Expected a value after '=' but found none. Implicit nulls are disallowed by 'treatEmptyValueAsNull = disallow'.`, `Write 'null' explicitly (${resultKey} = null) if that is intended, or provide a concrete value.`);
|
|
430
434
|
return makeScalarValue('Undefined', undefined, 'Missing value already reported');
|
|
431
435
|
}
|
|
@@ -459,12 +463,14 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
459
463
|
)
|
|
460
464
|
}*/
|
|
461
465
|
if (!valueNode) {
|
|
466
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
462
467
|
this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(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 '-'.`);
|
|
463
468
|
}
|
|
464
469
|
else if (valueNode.type === 'Undefined' &&
|
|
465
470
|
valueNode.tag !== 'Invalid string literal already reported' &&
|
|
466
471
|
valueNode.tag !== 'Missing value already reported' &&
|
|
467
472
|
valueNode.tag !== 'Parser syntax error already reported') {
|
|
473
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
468
474
|
this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(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 '-'.`);
|
|
469
475
|
}
|
|
470
476
|
// It keeps the sentinel tags useful for control flow inside visitMember(..),
|
|
@@ -622,6 +628,7 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
622
628
|
'Check that all escape sequences in the C-string are complete and valid.';
|
|
623
629
|
}
|
|
624
630
|
}
|
|
631
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
625
632
|
this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', msgWhat, msgWhy, msgHint);
|
|
626
633
|
return makeScalarValue('Undefined', undefined, 'Invalid string literal already reported');
|
|
627
634
|
}
|
|
@@ -687,8 +694,14 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
687
694
|
// visitNull_literal?: (ctx: Null_literalContext) => Result
|
|
688
695
|
this.visitNull_literal = (ctx) => {
|
|
689
696
|
(0, print_1.debugPrint)('-> Entered visitNull_literal(..)');
|
|
690
|
-
|
|
691
|
-
return makeScalarValue('Null', null, 'Explicit Null')
|
|
697
|
+
// debugPrint('raw = ' + ctx.getText())
|
|
698
|
+
// return makeScalarValue('Null', null, 'Explicit Null')
|
|
699
|
+
const raw = ctx.getText();
|
|
700
|
+
(0, print_1.debugPrint)('raw: "' + raw + '"');
|
|
701
|
+
const parsed = (0, parseNull_1.default)(raw);
|
|
702
|
+
(0, print_1.debugPrint)('parsed: "' + parsed + '"');
|
|
703
|
+
// Case-insensitive true/false/on/off/yes/no (Spec section, 8.1).
|
|
704
|
+
return makeScalarValue('Null', parsed, 'Explicit Null');
|
|
692
705
|
};
|
|
693
706
|
/**
|
|
694
707
|
* Visit a parse tree produced by `YiniParser.list_literal`.
|
|
@@ -767,7 +780,8 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
767
780
|
this.visitObject_members = (ctx) => {
|
|
768
781
|
(0, print_1.debugPrint)('-> Entered visitObject_members(..)');
|
|
769
782
|
(0, print_1.debugPrint)('entries.length = ' + ctx?.object_member_list().length);
|
|
770
|
-
const entries = []
|
|
783
|
+
// const entries: Array<{ k: string; v: TValueLiteral }> = []
|
|
784
|
+
const entries = {};
|
|
771
785
|
ctx.object_member_list().forEach((member) => {
|
|
772
786
|
const { key, value } = this.visitObject_member(member);
|
|
773
787
|
(0, print_1.debugPrint)(' key = ' + key);
|
|
@@ -790,7 +804,7 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
790
804
|
const rawValue = ctx.value().getText();
|
|
791
805
|
const valueNode = ctx.value()
|
|
792
806
|
? this.visitValue(ctx.value())
|
|
793
|
-
: makeScalarValue('Null', 'Implicit Null');
|
|
807
|
+
: makeScalarValue('Null', null, 'Implicit Null');
|
|
794
808
|
(0, print_1.debugPrint)(' rawKey = ' + rawKey);
|
|
795
809
|
(0, print_1.debugPrint)(' key = ' + key);
|
|
796
810
|
(0, print_1.debugPrint)('rawValue = ' + rawValue);
|
|
@@ -806,30 +820,32 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
806
820
|
return { key, value: valueNode };
|
|
807
821
|
};
|
|
808
822
|
/**
|
|
809
|
-
*
|
|
810
|
-
* @param ctx the parse tree
|
|
811
|
-
* @grammarRule KEY WS? COLON (eol | WS+)* elements (eol | WS+)* eol
|
|
812
|
-
* @return the visitor result
|
|
823
|
+
* @note Colon list not supported any more since YINI Spec Package v1.0.0.rc4
|
|
813
824
|
*/
|
|
814
|
-
// visitColon_list_decl
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
825
|
+
// visitColon_list_decl = (ctx: Colon_list_declContext): any => {
|
|
826
|
+
// debugPrint('-> Entered visitColon_list_decl(..)')
|
|
827
|
+
// const key = ctx.getChild(0).getText()
|
|
828
|
+
// debugPrint(`visitColon_list_decl(..): key = '${key}'`)
|
|
829
|
+
// const elems = this.visitElements(ctx.elements())
|
|
830
|
+
// const value = makeListValue(elems, 'From colon-list')
|
|
831
|
+
// const current = this.sectionStack[this.sectionStack.length - 1]
|
|
832
|
+
// // putMember(current, key, list, this.ast, this.onDuplicateKey)
|
|
833
|
+
// this.putMember(
|
|
834
|
+
// this.errorHandler!,
|
|
835
|
+
// ctx,
|
|
836
|
+
// current,
|
|
837
|
+
// key,
|
|
838
|
+
// value,
|
|
839
|
+
// // this.ast,
|
|
840
|
+
// this.onDuplicateKey,
|
|
841
|
+
// )
|
|
842
|
+
// debugPrint('<- About to exit visitColon_list_decl(..)...')
|
|
843
|
+
// if (isDebug()) {
|
|
844
|
+
// console.log('List literal: (from a Colon-list)')
|
|
845
|
+
// printObject(value)
|
|
846
|
+
// }
|
|
847
|
+
// return value
|
|
848
|
+
// }
|
|
833
849
|
/**
|
|
834
850
|
* Visit a parse tree produced by `YiniParser.string_concat`.
|
|
835
851
|
* @param ctx the parse tree
|
|
@@ -864,6 +880,7 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
864
880
|
}
|
|
865
881
|
catch (err) {
|
|
866
882
|
const msg = err instanceof Error ? err.message : String(err);
|
|
883
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
867
884
|
this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Parse error in string', msg);
|
|
868
885
|
return undefined;
|
|
869
886
|
}
|
|
@@ -921,6 +938,22 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
921
938
|
};
|
|
922
939
|
this.sectionStack = [root];
|
|
923
940
|
}
|
|
941
|
+
// --- Private helper methods --------------------------------
|
|
942
|
+
validateStrictTopLevelStructure() {
|
|
943
|
+
if (!this.isStrict)
|
|
944
|
+
return;
|
|
945
|
+
const numTopLevelSections = this.ast.root.children.length;
|
|
946
|
+
const numTopLevelMembers = this.ast.root.members.size;
|
|
947
|
+
if (numTopLevelMembers > 0) {
|
|
948
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
949
|
+
this.errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Top-level members are not allowed in strict mode.', 'Members were found outside the single required explicit top-level section.', 'Move all top-level members into the explicit top-level section, or parse the document in lenient mode.');
|
|
950
|
+
}
|
|
951
|
+
// Exactly one explicit top-level section in strict mode.
|
|
952
|
+
if (numTopLevelSections !== 1) {
|
|
953
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
954
|
+
this.errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Strict mode requires exactly one explicit top-level section.', `Found ${numTopLevelSections} explicit top-level section${numTopLevelSections === 1 ? '' : 's'}.`, 'Wrap the document in exactly one explicit top-level section and nest any additional sections beneath it.');
|
|
955
|
+
}
|
|
956
|
+
}
|
|
924
957
|
extractStringParts(tokenText) {
|
|
925
958
|
// Detect prefix
|
|
926
959
|
let prefix = '';
|
|
@@ -1042,12 +1075,15 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
1042
1075
|
if (sec.members.has(key)) {
|
|
1043
1076
|
switch (mode) {
|
|
1044
1077
|
case 'error':
|
|
1078
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
1045
1079
|
errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Hit a duplicate key in this section and scope', `Key '${key}' already exists in section '${sec.sectionName}' on level ${sec.level}.`);
|
|
1046
1080
|
break;
|
|
1047
1081
|
case 'warn-and-keep-first':
|
|
1082
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
1048
1083
|
errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Warning', `Hit a duplicate key (will keep first value) in this section and scope`, `Key '${key}' already exists in section '${sec.sectionName}' on level ${sec.level}.`);
|
|
1049
1084
|
return; // Keep first, don't overwrite.
|
|
1050
1085
|
case 'warn-and-overwrite':
|
|
1086
|
+
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
1051
1087
|
errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Warning', `Overwrote a duplicate key (will keep last value) in this section and scope`, `Key '${key}' was overwritten in section '${sec.sectionName}' on level ${sec.level}.`);
|
|
1052
1088
|
break; // Overwrite, replace value.
|
|
1053
1089
|
case 'keep-first':
|
|
@@ -1065,30 +1101,25 @@ class ASTBuilder extends YiniParserVisitor_1.default {
|
|
|
1065
1101
|
// Public entry
|
|
1066
1102
|
buildAST(ctx) {
|
|
1067
1103
|
this.visitYini?.(ctx);
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1104
|
+
// Strict-mode structural validation.
|
|
1105
|
+
this.validateStrictTopLevelStructure();
|
|
1106
|
+
const isMissingTerminator = !this.ast.terminatorSeen;
|
|
1107
|
+
// In strict mode, the document terminator is required by default.
|
|
1108
|
+
// However, the parse option `requireDocTerminator` is authoritative
|
|
1109
|
+
// and may override that default behavior.
|
|
1110
|
+
const terminatorPolicy = this.options.rules.requireDocTerminator;
|
|
1111
|
+
if (isMissingTerminator && terminatorPolicy === 'required') {
|
|
1076
1112
|
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
1077
|
-
this.errorHandler.pushOrBail(undefined, 'Syntax-Error',
|
|
1113
|
+
this.errorHandler.pushOrBail(undefined, 'Syntax-Error', "Missing '/END' at end of document.", "The document terminator '/END' (case-insensitive) is required at the end of the document.", "Add '/END' as the final significant line, or change requireDocTerminator to 'optional' or 'warn-if-missing'.");
|
|
1078
1114
|
}
|
|
1079
|
-
else if (
|
|
1080
|
-
|
|
1081
|
-
const msgWhat = `Missing '/END' at end of document (option requireDocTerminator is ${this.options.rules.requireDocTerminator}).`;
|
|
1082
|
-
const msgWhy = `The terminator '/END' (case insensitive) might be missing at the end of the document.`;
|
|
1083
|
-
const msgHint = `This is option can be overriden by the option requireDocTerminator.`;
|
|
1115
|
+
else if (isMissingTerminator &&
|
|
1116
|
+
terminatorPolicy === 'warn-if-missing') {
|
|
1084
1117
|
// Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
|
|
1085
|
-
this.errorHandler.pushOrBail(undefined, 'Syntax-Warning',
|
|
1118
|
+
this.errorHandler.pushOrBail(undefined, 'Syntax-Warning', "Missing '/END' at end of document.", "The document terminator '/END' (case-insensitive) appears to be missing at the end of the document.", "Add '/END' as the final significant line, or change requireDocTerminator to 'optional'.");
|
|
1086
1119
|
}
|
|
1087
|
-
// Note: Below is important for error checking as well as for meta data.
|
|
1088
1120
|
this.ast.numOfSections = this.mapSectionNamePaths.size;
|
|
1089
1121
|
this.ast.numOfMembers = this._numOfMembers;
|
|
1090
1122
|
if (this.options.isIncludeMeta) {
|
|
1091
|
-
// Attach collected meta information.
|
|
1092
1123
|
this.ast.maxDepth = this.meta_maxLevel;
|
|
1093
1124
|
this.ast.sectionNamePaths = [...this.mapSectionNamePaths.keys()];
|
|
1094
1125
|
}
|