yini-parser 1.4.2-beta → 1.4.3

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.4.3 - 2026 Apr
4
+ - **Fixed:** Rebuilt the project and reduced reported vulnerabilities from 4 to 0.
5
+
6
+ ## 1.4.3-beta - 2026 Mar
7
+ - **Fixed:** Error messages and thrown parse errors now include correct line and column information again.
8
+ - **Improved:** Syntax and string-related parse errors are now clearer and more consistent.
9
+ - **Improved:** Reduced some duplicate follow-up errors during recovery after invalid input.
10
+ - **Added:** Regression tests for diagnostics, thrown errors, and line/column reporting.
11
+
3
12
  ## 1.4.2-beta - 2026 Mar
4
13
  - **Fixed:** Error messages now include line and column information again. This was unintentionally missing after the previous update.
5
14
  - **Added:** Test cases to help ensure line and column information is preserved in future updates.
package/README.md CHANGED
@@ -1,145 +1,68 @@
1
- # YINI Parser for Node.js
2
- > **Readable configuration without YAML foot-guns or JSON noise.**
1
+ # yini-parser
2
+ > **Readable configuration for Node.js and TypeScript — without YAML foot-guns or JSON noise.**
3
3
 
4
- The official TypeScript / Node.js parser for **YINI** (by the YINI-lang project) — an INI-inspired, human-readable text format for structured information.
4
+ The official TypeScript / Node.js parser for **YINI** (by the YINI-lang project) — a human-friendly configuration format with real structure, nested sections, comments, and predictable parsing.
5
+
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.
5
7
 
6
8
  [![npm version](https://img.shields.io/npm/v/yini-parser.svg)](https://www.npmjs.com/package/yini-parser) [![All Test Suites](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-all-tests.yml) [![All Regression Tests](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-regression-tests.yml) [![Grammar Drift Check](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml/badge.svg)](https://github.com/YINI-lang/yini-parser-typescript/actions/workflows/run-grammar-drift-check.yml) [![npm downloads](https://img.shields.io/npm/dm/yini-parser)](https://www.npmjs.com/package/yini-parser)
7
9
 
8
- Designed to bring structure, clarity, and predictable behavior to real-world configuration needs. Well suited for applications, tools, and services that need readable yet structured configuration.
10
+ ## Quick Start
9
11
 
10
12
  ```sh
11
13
  npm install yini-parser
12
14
  ```
13
15
 
14
- ```ts
15
- import YINI from 'yini-parser'
16
- const config = YINI.parseFile('./config.yini')
17
- ```
18
-
19
- ```sh
20
- npm run test
21
- ```
22
-
23
- ➡️ See full [documentation or YINI format specification](https://github.com/YINI-lang/YINI-spec)
24
-
25
- ---
26
-
27
- ## Example of YINI code
28
- > A basic YINI configuration example, showing a section, nested section, comments:
29
- ![YINI Config Example](./samples/basic.yini.png)
30
- Source: [basic.yini](./samples/basic.yini)
31
-
32
- ## YINI Parser – (source code in TypeScript)
33
-
34
- A runtime parser for the official [YINI configuration file format](https://github.com/YINI-lang/YINI-spec).
35
-
36
- The parser follows the official YINI specification and is implemented in TypeScript.
37
-
38
- ---
39
-
40
- ## Quick Start
41
-
42
- A small example using YINI in TypeScript/JavaScript:
43
16
  ```ts
44
17
  import YINI from 'yini-parser'
45
18
 
46
19
  const config = YINI.parse(`
47
- // YINI is a simple, human-readable configuration file format.
48
-
49
- // Note: In YINI, spaces and tabs never change meaning.
50
- // Indentation is only for readability.
20
+ ^ App
21
+ name = 'My App'
22
+ darkMode = true
51
23
 
52
- ^ App // Definition of section (group) "App"
53
- name = 'My Title' // Keys and values are written as key = value
54
- items = 25
55
- darkMode = true // "ON" and "YES" works too
56
-
57
- // Sub-section of the "App" section
58
- ^^ Special
59
- primaryColor = #336699 // Hex number format
60
- isCaching = false // "OFF" and "NO" works too
24
+ ^^ Features
25
+ caching = on
61
26
  `)
62
27
 
63
- // To parse from a file instead:
64
- // const config = YINI.parseFile('./config.yini')
65
-
66
- console.log(config.App.name) // My Title
67
- console.log(config.App.Special.isCaching) // false
68
- console.log()
69
- console.log(config)
70
- ```
71
-
72
- **Output:**
73
- ```js
74
- My Title
75
- false
76
-
77
- {
78
- App: {
79
- name: 'My Title',
80
- items: 25,
81
- darkMode: true,
82
- Special: {
83
- primaryColor: 3368601,
84
- isCaching: false
85
- }
86
- }
87
- }
28
+ console.log(config.App.name) // My App
29
+ console.log(config.App.Features.caching) // true
88
30
  ```
89
31
 
90
- That's it!
91
-
92
- - ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
93
-
94
- ---
95
-
96
- ## Example 2
97
- > A real-world YINI configuration example, showing sections, nesting, comments, and multiple data types:
98
- ![YINI Config Example](./samples/config.yini.png)
99
- Source: [config.yini](./samples/config.yini)
100
-
101
- ## 📂 More Examples
102
-
103
- - ▶️ Explore more [YINI examples](https://yini-lang.org/learn-yini/examples/?utm_source=yini-parser-ts&utm_medium=github&utm_campaign=repo-link&utm_content=readme).
104
-
105
- ---
106
-
107
- ### Who is this for?
108
-
109
- YINI is designed as an ideal option for application developers, platform teams, DevOps engineers, and anyone maintaining complex configuration at scale.
32
+ ➡️ Learn more in the [YINI specification and documentation](https://yini-lang.org/refs/specification).
110
33
 
111
34
  ---
112
35
 
113
- ## 🙋‍♀️ Why YINI?
114
- - **Indentation-independent structure:** The YINI config format is indentation-independent, meaning spaces and tabs never affect meaning.
115
- - **Explicit nesting for easy refactoring & large configs:** It uses clear header markers (`^`, `^^`, `^^^`) to define hierarchy (like in Markdown), without long dotted keys.
116
- - **Multiple data types:** Supports boolean literals (`true` / `false`, `Yes` / `No`, etc), numbers, arrays (lists), and JS-style objects natively, with explicit string syntax.
117
- - **Comments support:** YINI supports multiple comment styles (`#`, `//`, `/* ... */`, and `;`). Allows you to document configuration directly in the file.
118
- - **Predictable parsing rules:** Fewer production surprises, well-defined rules with optional strict and lenient modes, for different use cases.
36
+ ## 🙋‍♀️ Why try YINI?
119
37
 
38
+ - **Readable by humans** — Less noisy than JSON, less fragile than indentation-driven formats.
39
+ - **Structured enough for real configuration** — Sections, nested sections, lists, objects, booleans, and null.
40
+ - **Predictable parsing** — Explicit syntax with clear rules.
41
+ - **Easy to use from TypeScript/Node.js** — Parse from strings or files in a few lines.
42
+
120
43
  ---
121
44
 
122
- ## Features
123
- - Simple syntax (supports both strict and lenient modes).
124
- - Familiar config file style (inspired by INI, JSON, Python, and Markdown).
125
- - Easy programmatic usage.
126
- - Only the `YINI` class is exported; all internal details are private.
127
- - Arrays/Lists (bracketed): `list = [10, 20, 30]`
128
- - JavaScript-style objects (inline maps).
45
+ ## What YINI looks like in practice
46
+ > A basic YINI configuration example, showing a section, nested section, comments:
47
+ ![YINI Config Example](./samples/basic.yini.png)
48
+ Source: [basic.yini](./samples/basic.yini)
129
49
 
130
- If you find this useful, please consider starring the project on GitHub.
50
+ - ▶️ Link to [Demo Apps](https://github.com/YINI-lang/yini-demo-apps/tree/main) with complete basic usage.
131
51
 
132
52
  ---
133
53
 
134
- ## 🧪 Stability & Guarantees
135
-
136
- This parser is continuously validated through comprehensive regression and smoke tests, ensuring deterministic parsing behavior across default, strict, and metadata-enabled modes.
54
+ ## Why YINI works well for configuration
55
+ - **Indentation-independent structure:** Spaces and tabs never change meaning, so files can be reformatted without changing structure.
56
+ - **Explicit nesting:** Hierarchy is defined with section markers like `^`, `^^`, and `^^^`, making large configurations easier to scan and refactor.
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 easier to document configuration directly in the file.
59
+ - **Predictable parsing:** Clear rules with optional strict and lenient modes for different use cases.
137
60
 
138
61
  ---
139
62
 
140
63
  ## Usage
141
64
 
142
- ### Installation
65
+ ### Install with your package manager
143
66
 
144
67
  With **npm**:
145
68
  ```sh
@@ -160,8 +83,7 @@ pnpm add yini-parser
160
83
  **Note:** Only a default export (YINI) is provided. Named imports are not supported.
161
84
  ```js
162
85
  const YINI = require('yini-parser').default;
163
- // (!) If you get undefined, try:
164
- // (Some Node.js setups require the .default property, others don't, due to ESM/CommonJS interop quirks.)
86
+ // If your setup handles default interop differently, try:
165
87
  // const YINI = require('yini-parser');
166
88
 
167
89
  // Parse from string.
@@ -206,54 +128,50 @@ const configFromFile = YINI.parseFile('./config.yini');
206
128
 
207
129
  ---
208
130
 
209
- ## 🛠 Roadmap
210
-
211
- 1. **Improve existing functionality** — Ongoing improvements to core parsing, richer diagnostics, and expanded QA for areas not yet fully covered.
212
- 2. **Full spec compliance** — Complete support for all features in the YINI Specification v1.0.0 (and the latest RCs).
213
- - Progress and current status are tracked in [FEATURE-CHECKLIST.md](https://github.com/YINI-lang/yini-parser-typescript/blob/main/FEATURE-CHECKLIST.md).
214
- 3. **Schema validation (future)** — Possible future expansion to support schema/contract validation for stricter type-safe configs.
215
- 4. **Ecosystem integration** - Broader and additional support for tooling, and other ecosystem projects.
131
+ ## 📂 More Examples
216
132
 
217
- ### Planned & Upcoming Features
218
- Some advanced YINI features are still evolving and are tracked transparently.
133
+ - ▶️ Explore more [YINI examples](https://yini-lang.org/learn-yini/examples/?utm_source=yini-parser-ts&utm_medium=github&utm_campaign=repo-link&utm_content=readme).
219
134
 
220
- You can follow progress in the [YINI parser FEATURE-CHECKLIST](https://github.com/YINI-lang/yini-parser-typescript/blob/main/FEATURE-CHECKLIST.md). Contributions and feature requests are welcome!
135
+ ### Example 2
136
+ > A real-world YINI configuration example, showing sections, nesting, comments, and multiple data types:
137
+ ![YINI Config Example](./samples/config.yini.png)
138
+ Source: [config.yini](./samples/config.yini)
221
139
 
222
140
  ---
223
141
 
224
- ## 🤝 Contributing
225
- We welcome feedback, bug reports, feature requests, and code contributions!
226
- - [Open an Issue](https://github.com/YINI-lang/yini-parser-typescript/issues)
227
- - [Start a Discussion](https://github.com/YINI-lang/yini-parser-typescript/discussions)
228
-
229
-
230
- If this library is useful to you, a GitHub star helps guide future development.
231
-
232
- ---
142
+ ## 🧪 Testing and Stability
233
143
 
234
- ## 📚 Documentation
235
- - [Project Setup](https://github.com/YINI-lang/yini-parser-typescript/blob/main/docs/Project-Setup.md) — How to run, test, and build the project, etc.
236
- - [Conventions](https://github.com/YINI-lang/yini-parser-typescript/blob/main/docs/Conventions.md) — Project conventions, naming patterns, etc.
144
+ This parser is continuously validated through comprehensive regression and smoke tests, ensuring deterministic parsing behavior across default, strict, and metadata-enabled modes.
237
145
 
238
146
  ---
239
147
 
240
148
  ## Links
241
- - ➡️ [Read the YINI Specification](https://github.com/YINI-lang/YINI-spec/tree/production/YINI-Specification.md#table-of-contents)
242
- *Full formal spec for the YINI format, including syntax and features.*
149
+ - ➡️ [YINI Homepage](https://yini-lang.org)
150
+ *Tutorials, guides, and examples.*
243
151
 
244
152
  - ➡️ [YINI CLI on GitHub](https://github.com/YINI-lang/yini-cli)
245
- *TypeScript source code, issue tracker, and contributing guide.*
153
+ *CLI tooling for working with YINI files.*
246
154
 
247
155
  - ➡️ [YINI Project](https://github.com/YINI-lang)
248
- *YINI home on GitHub.*
156
+ *Repositories and related ecosystem projects.*
249
157
 
250
- - ➡️ [YINI Homepage](https://yini-lang.org)
251
- *Tutorials & Docs.*
158
+ ---
159
+
160
+ ## 🤝 Contributing
161
+ We welcome feedback, bug reports, feature requests, and code contributions!
162
+ - [Open an Issue](https://github.com/YINI-lang/yini-parser-typescript/issues)
163
+ - [Start a Discussion](https://github.com/YINI-lang/yini-parser-typescript/discussions)
164
+
165
+ If this library is useful to you, a GitHub star helps more people discover the project and supports future development.
166
+
167
+ ### Documentation
168
+ - [Project Setup](https://github.com/YINI-lang/yini-parser-typescript/blob/main/docs/Project-Setup.md) — How to run, test, and build the project, etc.
169
+ - [Conventions](https://github.com/YINI-lang/yini-parser-typescript/blob/main/docs/Conventions.md) — Project conventions, naming patterns, etc.
252
170
 
253
171
  ---
254
172
 
255
173
  ## License
256
- This project is licensed under the Apache-2.0 license - see the [LICENSE](<./LICENSE>) file for details.
174
+ This project is licensed under the Apache-2.0 license see the [LICENSE](./LICENSE) file for details.
257
175
 
258
176
  In this project on GitHub, the `libs` directory contains third party software and each is licensed under its own license which is described in its own license file under the respective directory under `libs`.
259
177
 
@@ -264,4 +182,4 @@ In this project on GitHub, the `libs` directory contains third party software an
264
182
  >
265
183
  > Predictable configuration with clear rules.
266
184
 
267
- [yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_parser_ts&utm_content=readme_footer) · [YINI on GitHub](https://github.com/YINI-lang)
185
+ [yini-lang.org](https://yini-lang.org/?utm_source=github&utm_medium=referral&utm_campaign=yini_parser_ts&utm_content=readme_footer) · [YINI-lang on GitHub](https://github.com/YINI-lang)
@@ -386,16 +386,39 @@ class ASTBuilder extends YiniParserVisitor_1.default {
386
386
  }
387
387
  }
388
388
  const resultKey = (0, string_1.trimBackticks)(rawKey);
389
+ const rawMemberText = ctx.getText();
389
390
  const rawValue = ctx.value?.()?.getText();
390
391
  (0, print_1.debugPrint)(`visitMember(..): rawValue = ` + ctx.value?.()?.getText());
392
+ (0, print_1.debugPrint)(`visitMember(..): rawMemberText = ` + rawMemberText);
393
+ /* NOTE:
394
+ ctx.value() can be missing after parser recovery, even when the user did type something after ""=".
395
+
396
+ Example:
397
+ port = 54_32
398
+
399
+ ANTLR may reject 54_32, and then ctx.value() may not exist in the AST as you expect.
400
+ So rawValue alone is not enough to know whether this was:
401
+ * truly empty: port =
402
+ * malformed: port = 54_32
403
+ * rawMemberText lets one inspect the whole member text.
404
+ */
391
405
  let valueContext = ctx.value?.();
392
406
  let valueNode;
393
- if (!rawValue) {
394
- // treatEmptyValueAsNull = 'allow' (default in lenient mode, empty value => Null in lenient mode)
395
- // if (!this.isStrict) {
407
+ const hasEquals = rawMemberText.includes('=');
408
+ // const hasTextAfterEquals = hasEquals
409
+ // ? rawMemberText.split('=').slice(1).join('=').trim().length > 0
410
+ // : false
411
+ const eqIndex = rawMemberText.indexOf('=');
412
+ const hasTextAfterEquals = eqIndex >= 0 && rawMemberText.slice(eqIndex + 1).trim().length > 0;
413
+ if (!valueContext || !rawValue) {
414
+ // Case 1: there is text after '=' but parser could not form a valid value.
415
+ // Parser-level syntax error has likely already been reported.
416
+ if (hasTextAfterEquals) {
417
+ return makeScalarValue('Undefined', undefined, 'Parser syntax error already reported');
418
+ }
419
+ // Case 2: truly empty value.
396
420
  switch (this.options.rules.treatEmptyValueAsNull) {
397
421
  case 'allow':
398
- // Lenient mode: implicit null, no warning (treatEmptyValueAsNull = 'allow').
399
422
  valueNode = makeScalarValue('Null', null, 'Implicit null (empty value)');
400
423
  break;
401
424
  case 'allow-with-warning':
@@ -403,41 +426,56 @@ class ASTBuilder extends YiniParserVisitor_1.default {
403
426
  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'.`);
404
427
  break;
405
428
  case 'disallow':
406
- // treatEmptyValueAsNull = 'disallow' (default in strict mode)
407
- // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
408
- 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.`);
409
- break;
429
+ 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
+ return makeScalarValue('Undefined', undefined, 'Missing value already reported');
410
431
  }
411
432
  }
412
433
  else {
413
- valueNode = this.visitValue?.(valueContext);
434
+ valueNode = this.visitValue(valueContext);
414
435
  }
415
436
  (0, print_1.debugPrint)('visitMember(..): valueNode:');
416
437
  if ((0, env_1.isDebug)()) {
417
438
  (0, print_1.printObject)(valueNode);
418
439
  }
419
- // if (!valueNode || valueNode.type === 'Undefined') {
420
- // // Note, after pushing processing may continue or exit, depending on the error and/or the bail threshold.
421
- // this.errorHandler!.pushOrBail(
422
- // toErrorLocation(ctx),
423
- // 'Syntax-Error',
424
- // 'Invalid value',
425
- // `Invalid value for key '${resultKey} in member (<key> = <value> pair)'.`,
426
- // `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`,
427
- // )
428
- // }
440
+ /*
441
+ if (!valueNode) {
442
+ this.errorHandler!.pushOrBail(
443
+ toErrorLocation(ctx),
444
+ 'Syntax-Error',
445
+ 'Invalid value',
446
+ `Invalid value for key '${resultKey}' in member (<key> = <value> pair).`,
447
+ `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`,
448
+ )
449
+ } else if (
450
+ valueNode.type === 'Undefined' &&
451
+ valueNode.tag !== 'Invalid string literal already reported'
452
+ ) {
453
+ this.errorHandler!.pushOrBail(
454
+ toErrorLocation(ctx),
455
+ 'Syntax-Error',
456
+ 'Invalid value',
457
+ `Invalid value for key '${resultKey}' in member (<key> = <value> pair).`,
458
+ `Got '${rawValue}', but expected a valid value/literal (string, number, boolean, null, list, or object). Optionally with a single leading minus sign '-'.`,
459
+ )
460
+ }*/
429
461
  if (!valueNode) {
430
462
  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 '-'.`);
431
463
  }
432
464
  else if (valueNode.type === 'Undefined' &&
433
- valueNode.tag !== 'Invalid string literal already reported') {
465
+ valueNode.tag !== 'Invalid string literal already reported' &&
466
+ valueNode.tag !== 'Missing value already reported' &&
467
+ valueNode.tag !== 'Parser syntax error already reported') {
434
468
  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 '-'.`);
435
469
  }
470
+ // It keeps the sentinel tags useful for control flow inside visitMember(..),
471
+ // but prevents them from leaking into the final parsed structure.
436
472
  const current = this.sectionStack[this.sectionStack.length - 1];
437
- if (valueNode !== undefined) {
438
- this.putMember(this.errorHandler, ctx, current, resultKey, valueNode,
439
- // this.ast,
440
- this.onDuplicateKey);
473
+ const shouldSkipMemberInsertion = valueNode?.type === 'Undefined' &&
474
+ (valueNode.tag === 'Invalid string literal already reported' ||
475
+ valueNode.tag === 'Missing value already reported' ||
476
+ valueNode.tag === 'Parser syntax error already reported');
477
+ if (!shouldSkipMemberInsertion && valueNode !== undefined) {
478
+ this.putMember(this.errorHandler, ctx, current, resultKey, valueNode, this.onDuplicateKey);
441
479
  }
442
480
  return valueNode;
443
481
  };
@@ -491,48 +529,104 @@ class ASTBuilder extends YiniParserVisitor_1.default {
491
529
  * @param ctx the parse tree
492
530
  * @return the visitor result
493
531
  */
494
- this.visitString_literal = (ctx) => {
495
- let text = '';
532
+ /*
533
+ visitString_literal = (ctx: String_literalContext): any => {
534
+ let text = ''
535
+
496
536
  const pieces = [
497
537
  ctx.STRING(),
498
538
  ...(ctx.string_concat_list()?.map((c) => c.STRING()) ?? []),
499
- ];
539
+ ]
540
+
500
541
  try {
501
542
  for (const token of pieces) {
502
- const tokenText = token.getText();
503
- const parsed = this.extractStringKindAndValue(tokenText);
504
- // text += parseStringLiteral(parsed)
505
- let txt = '';
543
+ const tokenText = token.getText()
544
+ const parsed = this.extractStringKindAndValue(tokenText)
545
+
506
546
  try {
507
- text += (0, parseString_1.default)(parsed);
508
- }
509
- catch (err) {
510
- const msg = '' + err?.message;
511
- this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Parse error in string', `${msg}`);
547
+ text += parseStringLiteral(parsed)
548
+ } catch (err: unknown) {
549
+ const msg = '' + (<any>err)?.message
550
+ this.errorHandler!.pushOrBail(
551
+ toErrorLocation(ctx),
552
+ 'Syntax-Error',
553
+ 'Parse error in string',
554
+ `${msg}`,
555
+ )
512
556
  }
513
557
  }
514
- return makeScalarValue('String', text);
515
- }
516
- catch (err) {
517
- const msg = err instanceof Error ? err.message : String(err);
518
- let msgWhat = 'Invalid string literal';
519
- let msgWhy = msg;
520
- let msgHint = '';
521
- if (err instanceof parseString_1.CYiniStringParseError) {
522
- msgWhat = 'Invalid escape sequence in string';
523
- msgWhy = msg;
558
+
559
+ return makeScalarValue('String', text)
560
+ } catch (err: unknown) {
561
+ const msg = err instanceof Error ? err.message : String(err)
562
+
563
+ let msgWhat = 'Invalid string literal'
564
+ let msgWhy = msg
565
+ let msgHint = ''
566
+
567
+ if (err instanceof CYiniStringParseError) {
568
+ msgWhat = 'Invalid escape sequence in string'
569
+ msgWhy = msg
570
+
524
571
  if (/Invalid escape sequence \\\\/.test(msg)) {
525
572
  msgHint =
526
- 'Use double backslashes (\\\\) in C-strings, or use a raw string for file paths.';
527
- }
528
- else if (/end of string/i.test(msg)) {
573
+ 'Use double backslashes (\\\\) in C-strings, or use a raw string for file paths.'
574
+ } else if (/end of string/i.test(msg)) {
529
575
  msgHint =
530
- 'Check that all escape sequences in the C-string are complete and valid.';
576
+ 'Check that all escape sequences in the C-string are complete and valid.'
531
577
  }
532
578
  }
533
- this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', msgWhat, msgWhy, msgHint);
534
- return makeScalarValue('Undefined', undefined, 'Invalid string literal');
579
+
580
+ this.errorHandler!.pushOrBail(
581
+ toErrorLocation(ctx),
582
+ 'Syntax-Error',
583
+ msgWhat,
584
+ msgWhy,
585
+ msgHint,
586
+ )
587
+
588
+ return makeScalarValue(
589
+ 'Undefined',
590
+ undefined,
591
+ 'Invalid string literal',
592
+ )
535
593
  }
594
+ }
595
+ */
596
+ this.visitString_literal = (ctx) => {
597
+ let text = '';
598
+ const pieces = [
599
+ ctx.STRING(),
600
+ ...(ctx.string_concat_list()?.map((c) => c.STRING()) ?? []),
601
+ ];
602
+ for (const token of pieces) {
603
+ const tokenText = token.getText();
604
+ const parsed = this.extractStringKindAndValue(tokenText);
605
+ try {
606
+ text += (0, parseString_1.default)(parsed);
607
+ }
608
+ catch (err) {
609
+ const msg = err instanceof Error ? err.message : String(err);
610
+ let msgWhat = 'Parse error in string';
611
+ let msgWhy = msg;
612
+ let msgHint = '';
613
+ if (err instanceof parseString_1.CYiniStringParseError) {
614
+ if (/Invalid escape sequence/i.test(msg)) {
615
+ msgWhat = 'Invalid escape sequence in string';
616
+ msgHint =
617
+ 'Use double backslashes (\\\\) in C-strings, or use a raw string if escapes are not needed.';
618
+ }
619
+ else if (/end of string/i.test(msg)) {
620
+ msgWhat = 'Incomplete escape sequence in string';
621
+ msgHint =
622
+ 'Check that all escape sequences in the C-string are complete and valid.';
623
+ }
624
+ }
625
+ this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', msgWhat, msgWhy, msgHint);
626
+ return makeScalarValue('Undefined', undefined, 'Invalid string literal already reported');
627
+ }
628
+ }
629
+ return makeScalarValue('String', text);
536
630
  };
537
631
  /**
538
632
  * Visit a parse tree produced by `YiniParser.number_literal`.
@@ -741,17 +835,37 @@ class ASTBuilder extends YiniParserVisitor_1.default {
741
835
  * @param ctx the parse tree
742
836
  * @return the visitor result
743
837
  */
838
+ /*
839
+ visitString_concat = (ctx: String_concatContext): any => {
840
+ const rawText = ctx.STRING().getText() // The token text.
841
+ const parsedInput = this.extractStringKindAndValue(rawText)
842
+ // return parseStringLiteral(parsedInput)
843
+
844
+ let txt = ''
845
+ try {
846
+ txt = parseStringLiteral(parsedInput)
847
+ } catch (err) {
848
+ const msg = '' + (<any>err)?.message
849
+ this.errorHandler!.pushOrBail(
850
+ toErrorLocation(ctx),
851
+ 'Syntax-Error',
852
+ 'Parse error in string',
853
+ `${msg}`,
854
+ )
855
+ }
856
+ }
857
+ */
858
+ //@ todo (?) Check that this function actually works, not sure this function is finished.
744
859
  this.visitString_concat = (ctx) => {
745
- const rawText = ctx.STRING().getText(); // The token text.
860
+ const rawText = ctx.STRING().getText();
746
861
  const parsedInput = this.extractStringKindAndValue(rawText);
747
- // return parseStringLiteral(parsedInput)
748
- let txt = '';
749
862
  try {
750
- txt = (0, parseString_1.default)(parsedInput);
863
+ return (0, parseString_1.default)(parsedInput);
751
864
  }
752
865
  catch (err) {
753
- const msg = '' + err?.message;
754
- this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Parse error in string', `${msg}`);
866
+ const msg = err instanceof Error ? err.message : String(err);
867
+ this.errorHandler.pushOrBail((0, errorDataHandler_1.toErrorLocation)(ctx), 'Syntax-Error', 'Parse error in string', msg);
868
+ return undefined;
755
869
  }
756
870
  };
757
871
  /**