yini-parser 1.4.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +187 -35
  3. package/dist/YINI.d.ts +29 -18
  4. package/dist/YINI.js +104 -3
  5. package/dist/core/astBuilder.d.ts +94 -18
  6. package/dist/core/astBuilder.js +439 -376
  7. package/dist/core/errorDataHandler.d.ts +6 -1
  8. package/dist/core/errorDataHandler.js +30 -43
  9. package/dist/core/internalTypes.d.ts +10 -1
  10. package/dist/core/objectBuilder.d.ts +8 -4
  11. package/dist/core/objectBuilder.js +47 -62
  12. package/dist/core/options/defaultParserOptions.d.ts +3 -2
  13. package/dist/core/options/defaultParserOptions.js +11 -2
  14. package/dist/core/options/optionsFunctions.js +6 -4
  15. package/dist/core/parsingRules/modeFromRulesMatcher.d.ts +1 -1
  16. package/dist/core/parsingRules/modeFromRulesMatcher.js +22 -13
  17. package/dist/core/pipeline/pipeline.js +35 -10
  18. package/dist/core/runtime.js +28 -19
  19. package/dist/grammar/generated/YiniLexer.d.ts +40 -53
  20. package/dist/grammar/generated/YiniLexer.js +357 -356
  21. package/dist/grammar/generated/YiniParser.d.ts +174 -118
  22. package/dist/grammar/generated/YiniParser.js +1185 -929
  23. package/dist/grammar/generated/YiniParserVisitor.d.ts +82 -19
  24. package/dist/grammar/generated/YiniParserVisitor.js +1 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +4 -3
  27. package/dist/parsers/extractHeaderParts.d.ts +12 -19
  28. package/dist/parsers/extractHeaderParts.js +57 -46
  29. package/dist/parsers/parseNumber.d.ts +24 -6
  30. package/dist/parsers/parseNumber.js +114 -49
  31. package/dist/parsers/parseSectionHeader.d.ts +11 -3
  32. package/dist/parsers/parseSectionHeader.js +55 -43
  33. package/dist/parsers/parseString.js +39 -20
  34. package/dist/parsers/validateShebangPlacement.d.ts +3 -0
  35. package/dist/parsers/validateShebangPlacement.js +52 -0
  36. package/dist/types/index.d.ts +20 -3
  37. package/dist/utils/print.d.ts +1 -0
  38. package/dist/utils/print.js +5 -1
  39. package/dist/utils/string.d.ts +1 -0
  40. package/dist/utils/string.js +17 -1
  41. package/dist/utils/system.d.ts +1 -0
  42. package/dist/utils/system.js +6 -1
  43. package/dist/utils/yiniHelpers.d.ts +44 -2
  44. package/dist/utils/yiniHelpers.js +134 -46
  45. package/examples/compare-formats.md +1 -1
  46. package/examples/nested.yini +1 -1
  47. package/package.json +11 -3
  48. package/dist/YINI.js.map +0 -1
  49. package/dist/config/env.js.map +0 -1
  50. package/dist/core/astBuilder.js.map +0 -1
  51. package/dist/core/errorDataHandler.js.map +0 -1
  52. package/dist/core/internalTypes.js.map +0 -1
  53. package/dist/core/objectBuilder.js.map +0 -1
  54. package/dist/core/options/defaultParserOptions.js.map +0 -1
  55. package/dist/core/options/failLevel.js.map +0 -1
  56. package/dist/core/options/optionsFunctions.js.map +0 -1
  57. package/dist/core/parsingRules/modeFromRulesMatcher.js.map +0 -1
  58. package/dist/core/parsingRules/rulesConstAndGuards.js.map +0 -1
  59. package/dist/core/pipeline/errorListeners.js.map +0 -1
  60. package/dist/core/pipeline/pipeline.js.map +0 -1
  61. package/dist/core/resultMetadataBuilder.js.map +0 -1
  62. package/dist/core/runtime.js.map +0 -1
  63. package/dist/dev/main.d.ts +0 -1
  64. package/dist/dev/main.js +0 -168
  65. package/dist/dev/main.js.map +0 -1
  66. package/dist/dev/quick-test-samples/defect-inputs.d.ts +0 -37
  67. package/dist/dev/quick-test-samples/defect-inputs.js +0 -106
  68. package/dist/dev/quick-test-samples/defect-inputs.js.map +0 -1
  69. package/dist/dev/quick-test-samples/valid-inputs.d.ts +0 -21
  70. package/dist/dev/quick-test-samples/valid-inputs.js +0 -422
  71. package/dist/dev/quick-test-samples/valid-inputs.js.map +0 -1
  72. package/dist/grammar/generated/YiniLexer.js.map +0 -1
  73. package/dist/grammar/generated/YiniParser.js.map +0 -1
  74. package/dist/grammar/generated/YiniParserVisitor.js.map +0 -1
  75. package/dist/index.js.map +0 -1
  76. package/dist/parsers/extractHeaderParts.js.map +0 -1
  77. package/dist/parsers/extractSignificantYiniLine.js.map +0 -1
  78. package/dist/parsers/parseBoolean.js.map +0 -1
  79. package/dist/parsers/parseNull.js.map +0 -1
  80. package/dist/parsers/parseNumber.js.map +0 -1
  81. package/dist/parsers/parseSectionHeader.js.map +0 -1
  82. package/dist/parsers/parseString.js.map +0 -1
  83. package/dist/types/index.js.map +0 -1
  84. package/dist/utils/number.js.map +0 -1
  85. package/dist/utils/object.js.map +0 -1
  86. package/dist/utils/pathAndFileName.js.map +0 -1
  87. package/dist/utils/print.js.map +0 -1
  88. package/dist/utils/string.js.map +0 -1
  89. package/dist/utils/system.js.map +0 -1
  90. package/dist/utils/yiniHelpers.js.map +0 -1
@@ -8,6 +8,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.runPipeline = void 0;
11
+ // src/core/pipeline/pipeline.ts
11
12
  const perf_hooks_1 = require("perf_hooks");
12
13
  const antlr4_1 = require("antlr4");
13
14
  const env_1 = require("../../config/env");
@@ -48,6 +49,9 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
48
49
  (0, print_1.debugPrint)('coreOptions.isThrowOnError = ' + coreOptions.isThrowOnError);
49
50
  (0, print_1.debugPrint)(' runtimeInfo.sourceType = ' + runtimeInfo.sourceType);
50
51
  (0, print_1.debugPrint)(' runtimeInfo.fileName = ' + runtimeInfo.fileName);
52
+ (0, print_1.debugPrint)(' bailSensitivity = ' + coreOptions.bailSensitivity);
53
+ (0, print_1.debugPrint)('coreOptions.isThrowOnError = ' + coreOptions.isThrowOnError);
54
+ (0, print_1.debugPrint)(' treatEmptyValueAsNull = ' + coreOptions.rules.treatEmptyValueAsNull);
51
55
  // let persistThreshold: TBailSensitivityLevel
52
56
  // switch (coreOptions.bailSensitivity) {
53
57
  // case '0-Ignore-Errors':
@@ -59,12 +63,21 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
59
63
  // default:
60
64
  // persistThreshold = '2-Abort-Even-on-Warnings'
61
65
  // }
62
- const errorHandler = new errorDataHandler_1.ErrorDataHandler(runtimeInfo.sourceType, runtimeInfo.fileName, coreOptions.bailSensitivity, coreOptions.isQuiet, coreOptions.isSilent, coreOptions.isThrowOnError);
63
- if (yiniContent.trim() === '') {
64
- const isFileSourceType = runtimeInfo?.sourceType === 'File';
65
- // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
66
- errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Empty YINI document.', `The input is blank or contains only whitespace in the ${isFileSourceType ? 'YINI file' : 'YINI inline content'}.`, `Tip: Add at least one section '^ SectionName' or a key-value pair 'key = value' to make it a valid YINI file.`);
66
+ const errorHandler = new errorDataHandler_1.ErrorDataHandler(runtimeInfo.sourceType, runtimeInfo.fileName, coreOptions.bailSensitivity, coreOptions.isDiagnosticOutputEnabled, coreOptions.isQuiet, coreOptions.isSilent, coreOptions.isThrowOnError);
67
+ for (const issue of runtimeInfo.preflightIssues) {
68
+ errorHandler.pushOrBail(issue.locInput, issue.type, issue.msgWhat, issue.msgWhy, issue.msgHint);
67
69
  }
70
+ // if (yiniContent.trim() === '') {
71
+ // const isFileSourceType: boolean = runtimeInfo?.sourceType === 'File'
72
+ // // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
73
+ // errorHandler.pushOrBail(
74
+ // undefined,
75
+ // 'Syntax-Error',
76
+ // 'Empty YINI document.',
77
+ // `The input is blank or contains only whitespace in the ${isFileSourceType ? 'YINI file' : 'YINI inline content'}.`,
78
+ // `Tip: Add at least one section '^ SectionName' or a key-value pair 'key = value' to make it a valid YINI file.`,
79
+ // )
80
+ // }
68
81
  //---------------------------------------------
69
82
  // Note: Only computed when isWithTiming.
70
83
  let timeStartMs = 0;
@@ -136,8 +149,18 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
136
149
  const builder = new astBuilder_1.default(errorHandler, coreOptions, runtimeInfo.sourceType, runtimeInfo.fileName || null);
137
150
  const ast = builder.buildAST(parseTree);
138
151
  if (ast.numOfMembers === 0 && ast.numOfSections === 0) {
139
- // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
140
- errorHandler.pushOrBail(undefined, 'Syntax-Error', 'No meaningful content.', `No sections or members found in the ${ast.sourceType === 'File' ? 'YINI file' : 'YINI inline content'}.`, `${ast.sourceType === 'File' ? 'A valid YINI file' : 'Any valid YINI inline content'} must contain at least one section '^ SectionName' or a key–value pair 'key = value' to make it a valid YINI file.`);
152
+ // Lenient mode: empty document is allowed, but warning.
153
+ // Strict mode: empty document is invalid, error.
154
+ const sourceLabel = ast.sourceType === 'File' ? 'YINI file' : 'YINI inline content';
155
+ const sourceRequirement = ast.sourceType === 'File'
156
+ ? 'A valid YINI file must contain at least one section, for example: ^ SectionName, or a key-value pair, for example: key = value.'
157
+ : 'Valid YINI inline content must contain at least one section, for example: ^ SectionName, or a key-value pair, for example: key = value.';
158
+ if (ast.isStrict === true) {
159
+ errorHandler.pushOrBail(undefined, 'Syntax-Error', 'Empty YINI document.', `The ${sourceLabel} contains no meaningful content.`, sourceRequirement);
160
+ }
161
+ else {
162
+ errorHandler.pushOrBail(undefined, 'Syntax-Warning', 'Empty YINI document.', `The ${sourceLabel} contains no meaningful content.`, sourceRequirement);
163
+ }
141
164
  }
142
165
  if ((0, env_1.isDebug)()) {
143
166
  console.log();
@@ -176,8 +199,9 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
176
199
  // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
177
200
  errorHandler.pushOrBail(undefined, 'Syntax-Warning', 'Warning: Strict initialMode is not yet fully implemented.', 'Some validation rules may still be missing or incomplete.');
178
201
  if (coreOptions.bailSensitivity === '0-Ignore-Errors') {
179
- // IMPORTANT: If "silent" option is set, do not log anything to console!
180
- if (!coreOptions.isQuiet && !coreOptions.isSilent) {
202
+ if (coreOptions.isDiagnosticOutputEnabled &&
203
+ !coreOptions.isQuiet &&
204
+ !coreOptions.isSilent) {
181
205
  console.warn(`Warning: The initial mode was set to strict mode, but fail level is set to 'ignore-errors'. This combination is contradictory and might be a mistake.`);
182
206
  }
183
207
  }
@@ -204,7 +228,8 @@ const runPipeline = (yiniContent, coreOptions, runtimeInfo, _meta_userOpts) => {
204
228
  const constructedMetadata = (0, resultMetadataBuilder_1.buildResultMetadata)(params);
205
229
  (0, print_1.debugPrint)('getNumOfErrors(): ' + errorHandler.getNumOfErrors());
206
230
  // Print a summary line at the end if any errors or warnings.
207
- if (!coreOptions.isQuiet && !coreOptions.isSilent) {
231
+ if (coreOptions.isDiagnosticOutputEnabled &&
232
+ !coreOptions.isSilent) {
208
233
  const errors = errorHandler.getNumOfErrors();
209
234
  const warnings = errorHandler.getNumOfWarnings();
210
235
  // Notes:
@@ -16,8 +16,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
16
16
  var _YiniRuntime_runtime;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.YiniRuntime = void 0;
19
+ // src/core/runtime.ts
19
20
  const fs_1 = __importDefault(require("fs"));
20
21
  const env_1 = require("../config/env");
22
+ const validateShebangPlacement_1 = require("../parsers/validateShebangPlacement");
21
23
  const pathAndFileName_1 = require("../utils/pathAndFileName");
22
24
  const print_1 = require("../utils/print");
23
25
  const string_1 = require("../utils/string");
@@ -54,6 +56,7 @@ class YiniRuntime {
54
56
  timeIoMs: null,
55
57
  preferredBailSensitivity: null,
56
58
  sha256: null,
59
+ preflightIssues: [],
57
60
  };
58
61
  }
59
62
  // --- Single implementation --------------------------------------------
@@ -102,6 +105,19 @@ class YiniRuntime {
102
105
  includeMetadata,
103
106
  };
104
107
  }
108
+ const shebangIssue = (0, validateShebangPlacement_1.getShebangPlacementIssue)(yiniContent, userOpts.strictMode);
109
+ if (shebangIssue) {
110
+ __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").preflightIssues.push(shebangIssue);
111
+ }
112
+ const originalContent = yiniContent;
113
+ yiniContent = (0, validateShebangPlacement_1.stripBomAndValidShebang)(yiniContent);
114
+ if (originalContent.startsWith('\uFEFF')) {
115
+ (0, print_1.devPrint)('runParse(..): BOM was detected and stripped from UTF-8 content.');
116
+ }
117
+ if (originalContent.startsWith('#!') ||
118
+ originalContent.startsWith('\uFEFF#!')) {
119
+ (0, print_1.devPrint)('runParse(..): Shebang detected on first line and ignored.');
120
+ }
105
121
  if (userOpts.includeMetadata && __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").sourceType === 'Inline') {
106
122
  const lineCount = yiniContent.split(/\r?\n/).length; // Counts the lines.
107
123
  const sha256 = (0, string_1.computeSha256)(yiniContent); // NOTE: Compute BEFORE any possible tampering of content.
@@ -111,9 +127,13 @@ class YiniRuntime {
111
127
  }
112
128
  // NOTE: Important: Do not trim or mutate the yiniContent here, due
113
129
  // to it will mess up the line numbers in error reporting.
114
- if (!yiniContent) {
115
- throw new Error('Syntax-Error: Unexpected blank YINI input');
130
+ // if (!yiniContent) {
131
+ // throw new Error('Syntax-Error: Unexpected blank YINI input')
132
+ // }
133
+ if (yiniContent === null || yiniContent === undefined) {
134
+ throw new Error('Syntax-Error: Missing YINI input');
116
135
  }
136
+ // IMPORTANT: Makes sure input ends with an empty NL!
117
137
  if (!yiniContent.endsWith('\n')) {
118
138
  yiniContent += '\n';
119
139
  }
@@ -166,13 +186,6 @@ class YiniRuntime {
166
186
  };
167
187
  }
168
188
  if ((0, pathAndFileName_1.getFileNameExtension)(filePath).toLowerCase() !== '.yini') {
169
- // IMPORTANT: If "silent" option is set, do not log anything to console!
170
- if (!userOpts.silent) {
171
- // In quiet-mode we still show errors (these are fine).
172
- console.error('Invalid file extension for YINI file:');
173
- console.error(`"${filePath}"`);
174
- console.error('File does not have a valid ".yini" extension (case-insensitive).');
175
- }
176
189
  throw new Error('Error: Unexpected file extension for YINI file');
177
190
  }
178
191
  // ---- Phase 0: I/O ----
@@ -192,22 +205,18 @@ class YiniRuntime {
192
205
  __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").preferredBailSensitivity = userOpts.failLevel;
193
206
  __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").sha256 = (0, string_1.computeSha256)(content); // NOTE: Compute BEFORE any possible tampering of content.
194
207
  }
195
- let hasNoNewlineAtEOF = false;
196
208
  if (!content.endsWith('\n')) {
209
+ __classPrivateFieldGet(this, _YiniRuntime_runtime, "f").preflightIssues.push({
210
+ locInput: undefined,
211
+ type: 'Syntax-Warning',
212
+ msgWhat: 'No newline at end of file.',
213
+ msgWhy: `It's recommended to end a YINI file with a newline. File: "${filePath}"`,
214
+ });
197
215
  content += '\n';
198
- hasNoNewlineAtEOF = true;
199
216
  }
200
217
  const result = this.runParse(content, {
201
218
  ...userOpts,
202
219
  });
203
- // if (hasNoNewlineAtEOF && !userOpts.quiet && !userOpts.silent) {
204
- if (hasNoNewlineAtEOF && !userOpts.quiet) {
205
- // IMPORTANT: If "silent" option is set, do not log anything to console!
206
- if (!userOpts.silent) {
207
- //@todo: (or maybe not, 20250917) Maybe let errorHandler emit message
208
- console.warn(`No newline at end of file, it's recommended to end a file with a newline. File:\n"${filePath}"`);
209
- }
210
- }
211
220
  return result;
212
221
  }
213
222
  }
@@ -1,62 +1,51 @@
1
- import { ATN, CharStream, DFA, Lexer, RuleContext } from "antlr4";
1
+ import { ATN, CharStream, DFA, Lexer } from "antlr4";
2
2
  export default class YiniLexer extends Lexer {
3
- static readonly YINI_TOKEN = 1;
4
- static readonly INCLUDE_TOKEN = 2;
5
- static readonly DEPRECATED_TOKEN = 3;
6
- static readonly SECTION_HEAD = 4;
3
+ static readonly SHEBANG = 1;
4
+ static readonly YINI_TOKEN = 2;
5
+ static readonly INCLUDE_TOKEN = 3;
6
+ static readonly DEPRECATED_TOKEN = 4;
7
7
  static readonly TERMINAL_TOKEN = 5;
8
- static readonly SS = 6;
9
- static readonly EUR = 7;
10
- static readonly CARET = 8;
11
- static readonly GT = 9;
12
- static readonly LT = 10;
13
- static readonly EQ = 11;
14
- static readonly HASH = 12;
15
- static readonly COMMA = 13;
16
- static readonly COLON = 14;
17
- static readonly OB = 15;
18
- static readonly CB = 16;
19
- static readonly OC = 17;
20
- static readonly CC = 18;
21
- static readonly PLUS = 19;
22
- static readonly DOLLAR = 20;
23
- static readonly PC = 21;
24
- static readonly AT = 22;
25
- static readonly SEMICOLON = 23;
26
- static readonly BOOLEAN_FALSE = 24;
27
- static readonly BOOLEAN_TRUE = 25;
28
- static readonly NULL = 26;
29
- static readonly EMPTY_OBJECT = 27;
30
- static readonly EMPTY_LIST = 28;
31
- static readonly SHEBANG = 29;
32
- static readonly NUMBER = 30;
33
- static readonly KEY = 31;
34
- static readonly IDENT = 32;
35
- static readonly IDENT_BACKTICKED = 33;
36
- static readonly STRING = 34;
37
- static readonly TRIPLE_QUOTED_STRING = 35;
38
- static readonly SINGLE_OR_DOUBLE = 36;
39
- static readonly R_AND_C_STRING = 37;
40
- static readonly HYPER_STRING = 38;
41
- static readonly ESC_SEQ = 39;
42
- static readonly ESC_SEQ_BASE = 40;
43
- static readonly NL = 41;
44
- static readonly SINGLE_NL = 42;
45
- static readonly WS = 43;
46
- static readonly BLOCK_COMMENT = 44;
47
- static readonly COMMENT = 45;
48
- static readonly LINE_COMMENT = 46;
49
- static readonly INLINE_COMMENT = 47;
50
- static readonly IDENT_INVALID = 48;
51
- static readonly REST = 49;
52
- static readonly META_INVALID = 50;
8
+ static readonly SECTION_HEAD = 6;
9
+ static readonly INVALID_SECTION_HEAD = 7;
10
+ static readonly BOOLEAN_FALSE = 8;
11
+ static readonly BOOLEAN_TRUE = 9;
12
+ static readonly NULL = 10;
13
+ static readonly EMPTY_OBJECT = 11;
14
+ static readonly EMPTY_LIST = 12;
15
+ static readonly STRING = 13;
16
+ static readonly NUMBER = 14;
17
+ static readonly CARET = 15;
18
+ static readonly SS = 16;
19
+ static readonly GT = 17;
20
+ static readonly LT = 18;
21
+ static readonly EQ = 19;
22
+ static readonly COMMA = 20;
23
+ static readonly COLON = 21;
24
+ static readonly OB = 22;
25
+ static readonly CB = 23;
26
+ static readonly OC = 24;
27
+ static readonly CC = 25;
28
+ static readonly PLUS = 26;
29
+ static readonly DOLLAR = 27;
30
+ static readonly PC = 28;
31
+ static readonly AT = 29;
32
+ static readonly SEMICOLON = 30;
33
+ static readonly NL = 31;
34
+ static readonly WS = 32;
35
+ static readonly BLOCK_COMMENT = 33;
36
+ static readonly DISABLED_LINE = 34;
37
+ static readonly FULL_LINE_COMMENT = 35;
38
+ static readonly INLINE_COMMENT = 36;
39
+ static readonly KEY = 37;
40
+ static readonly IDENT_INVALID = 38;
41
+ static readonly REST = 39;
42
+ static readonly META_INVALID = 40;
53
43
  static readonly EOF: number;
54
44
  static readonly channelNames: string[];
55
45
  static readonly literalNames: (string | null)[];
56
46
  static readonly symbolicNames: (string | null)[];
57
47
  static readonly modeNames: string[];
58
48
  static readonly ruleNames: string[];
59
- atLineStart(): boolean;
60
49
  constructor(input: CharStream);
61
50
  get grammarFileName(): string;
62
51
  get literalNames(): (string | null)[];
@@ -65,8 +54,6 @@ export default class YiniLexer extends Lexer {
65
54
  get serializedATN(): number[];
66
55
  get channelNames(): string[];
67
56
  get modeNames(): string[];
68
- sempred(localctx: RuleContext, ruleIndex: number, predIndex: number): boolean;
69
- private LINE_COMMENT_sempred;
70
57
  static readonly _serializedATN: number[];
71
58
  private static __ATN;
72
59
  static get _ATN(): ATN;