yini-parser 1.0.0-alpha.1
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 +0 -0
- package/LICENSE +201 -0
- package/README.md +120 -0
- package/dist/src/YINI.js +122 -0
- package/dist/src/config/env.js +15 -0
- package/dist/src/core/ErrorDataHandler.js +207 -0
- package/dist/src/core/YINIVisitor.js +856 -0
- package/dist/src/core/objectBuilder.js +166 -0
- package/dist/src/core/types.js +36 -0
- package/dist/src/grammar/YiniLexer.js +370 -0
- package/dist/src/grammar/YiniParser.js +2106 -0
- package/dist/src/grammar/YiniParserVisitor.js +14 -0
- package/dist/src/index.js +189 -0
- package/dist/src/parseEntry.js +155 -0
- package/dist/src/parsers/extractHeaderParts.js +103 -0
- package/dist/src/parsers/extractSignificantYiniLine.js +68 -0
- package/dist/src/parsers/parseBoolean.js +12 -0
- package/dist/src/parsers/parseNull.js +11 -0
- package/dist/src/parsers/parseNumber.js +49 -0
- package/dist/src/parsers/parseSectionHeader.js +111 -0
- package/dist/src/parsers/parseString.js +40 -0
- package/dist/src/utils/pathAndFileName.js +15 -0
- package/dist/src/utils/string.js +97 -0
- package/dist/src/utils/system.js +23 -0
- package/dist/src/yiniHelpers.js +141 -0
- package/dist/tests/integration/1-core-parsing/parse-bigger-section-nesting-as-object.test.js +83 -0
- package/dist/tests/integration/1-core-parsing/parse-section-nesting-w-classic-markers.test.js +170 -0
- package/dist/tests/integration/1-core-parsing/parse-section-nesting-w-nsh-markers.test.js +27 -0
- package/dist/tests/integration/1-core-parsing/read some values from level 1 and 2.test.js +77 -0
- package/dist/tests/integration/1-core-parsing/throw on bad section heads.test.js +162 -0
- package/dist/tests/integration/10-special-validation-modes/validation-modes.test.js +38 -0
- package/dist/tests/integration/2-file-structure-and-error/able to parse mixed case filenames.test.js +72 -0
- package/dist/tests/integration/2-file-structure-and-error/throw error on bad file extensions.test.js +36 -0
- package/dist/tests/integration/2-file-structure-and-error/throw error parsing bad content.test.js +80 -0
- package/dist/tests/smoke/A-general-smoke.test.js +259 -0
- package/dist/tests/smoke/B-parse-inline-smoke.test.js +270 -0
- package/dist/tests/smoke/C-traverse-file-smoke.test.js +141 -0
- package/dist/tests/smoke/D-parse-file-smoke.test.js +134 -0
- package/dist/tests/unit/parsers/extractHeaderParts.unit.test.js +490 -0
- package/dist/tests/unit/parsers/parseSectionHeader-classic.unit.test.js +421 -0
- package/dist/tests/unit/parsers/parseSectionHeader-nsh.unit.test.js +436 -0
- package/dist/tests/unit/parsers/parseSectionHeader-throw-on-invalid.unit.test.js +168 -0
- package/dist/tests/unit/utils/utils-pathAndFileName.unit.test.js +80 -0
- package/dist/tests/unit/utils/utils-string.unit.test.js +185 -0
- package/dist/tests/unit/yiniHelpers.unit.test.js +306 -0
- package/package.json +94 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Generated from grammar/v1.0.0-beta.7x/YiniParser.g4 by ANTLR 4.13.2
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const antlr4_1 = require("antlr4");
|
|
5
|
+
/**
|
|
6
|
+
* This interface defines a complete generic visitor for a parse tree produced
|
|
7
|
+
* by `YiniParser`.
|
|
8
|
+
*
|
|
9
|
+
* @param <Result> The return type of the visit operation. Use `void` for
|
|
10
|
+
* operations with no return type.
|
|
11
|
+
*/
|
|
12
|
+
class YiniParserVisitor extends antlr4_1.ParseTreeVisitor {
|
|
13
|
+
}
|
|
14
|
+
exports.default = YiniParserVisitor;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// import { isDebug&&console.log } from './utils/general'
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
var _a, _b, _c;
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.default = void 0;
|
|
9
|
+
/*
|
|
10
|
+
https://pauloe-me.medium.com/typescript-npm-package-publishing-a-beginners-guide-40b95908e69c
|
|
11
|
+
|
|
12
|
+
Run the code with the following command:
|
|
13
|
+
npx ts-node index
|
|
14
|
+
or
|
|
15
|
+
npm start
|
|
16
|
+
|
|
17
|
+
/END
|
|
18
|
+
*/
|
|
19
|
+
const env_1 = require("./config/env");
|
|
20
|
+
const system_1 = require("./utils/system");
|
|
21
|
+
const YINI_1 = __importDefault(require("./YINI"));
|
|
22
|
+
var YINI_2 = require("./YINI");
|
|
23
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(YINI_2).default; } });
|
|
24
|
+
if ((0, env_1.isDev)() || (0, env_1.isDebug)()) {
|
|
25
|
+
console.log('process.env?.NODE_ENV = ' + ((_a = process.env) === null || _a === void 0 ? void 0 : _a.NODE_ENV));
|
|
26
|
+
console.log('process.env?.APP_ENV = ' + ((_b = process.env) === null || _b === void 0 ? void 0 : _b.APP_ENV));
|
|
27
|
+
console.log('process.env?.IS_DEBUG = ' + ((_c = process.env) === null || _c === void 0 ? void 0 : _c.IS_DEBUG));
|
|
28
|
+
}
|
|
29
|
+
// console.log('NODE_ENV = ' + NODE_ENV)
|
|
30
|
+
// console.log('APP_ENV = ' + APP_ENV)
|
|
31
|
+
(0, system_1.debugPrint)();
|
|
32
|
+
(0, system_1.debugPrint)('-> Entered index.ts');
|
|
33
|
+
(0, system_1.debugPrint)();
|
|
34
|
+
const debugTestObj = {
|
|
35
|
+
name: 'e_test',
|
|
36
|
+
lang: 'TypeScript',
|
|
37
|
+
};
|
|
38
|
+
(0, system_1.debugPrint)('debugTestObj:');
|
|
39
|
+
(0, system_1.debugPrint)(debugTestObj);
|
|
40
|
+
(0, system_1.debugPrint)();
|
|
41
|
+
if ((0, env_1.isProd)()) {
|
|
42
|
+
// Do nothing, and exit.
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const invalidInput1 = `
|
|
46
|
+
^ Settings
|
|
47
|
+
fruit = "Pear"
|
|
48
|
+
number = 5
|
|
49
|
+
value q= "something"
|
|
50
|
+
`;
|
|
51
|
+
const invalidInput2 = `
|
|
52
|
+
^ Config
|
|
53
|
+
varAge = 30
|
|
54
|
+
varName = abcd
|
|
55
|
+
varNull = NULL
|
|
56
|
+
`;
|
|
57
|
+
const input1 = `
|
|
58
|
+
^ SectionName
|
|
59
|
+
varBool = true
|
|
60
|
+
varBool2 = off
|
|
61
|
+
varInt = 30
|
|
62
|
+
varFloat = 12.34
|
|
63
|
+
varStr = "Alice"
|
|
64
|
+
listItems = ["a", "b", "c"]
|
|
65
|
+
varE1 = 1e4
|
|
66
|
+
varE2 = 1.23e4
|
|
67
|
+
varE3 = 6.5E23
|
|
68
|
+
/END
|
|
69
|
+
`;
|
|
70
|
+
const input2 = `
|
|
71
|
+
^ Config
|
|
72
|
+
varAge = 30
|
|
73
|
+
varName = "Alice"
|
|
74
|
+
varNull = NULL
|
|
75
|
+
listItems = ["a", "b", "c"]
|
|
76
|
+
^^Extra
|
|
77
|
+
isExtra = true
|
|
78
|
+
/END
|
|
79
|
+
`;
|
|
80
|
+
// const input = `
|
|
81
|
+
// # Config`;
|
|
82
|
+
// debugPrint('input2:')
|
|
83
|
+
// if (isDebug()) {
|
|
84
|
+
// console.debug(input2)
|
|
85
|
+
// }
|
|
86
|
+
// YINI.parse(input2)
|
|
87
|
+
// debugPrint('invalidInput1:')
|
|
88
|
+
// if (isDebug()) {
|
|
89
|
+
// console.debug(invalidInput1)
|
|
90
|
+
// }
|
|
91
|
+
// YINI.parse(invalidInput1)
|
|
92
|
+
if (env_1.APP_ENV === 'local' && env_1.NODE_ENV !== 'test') {
|
|
93
|
+
/*
|
|
94
|
+
YINI.parse(`
|
|
95
|
+
--^ Section0
|
|
96
|
+
--value = 0
|
|
97
|
+
^ Section1
|
|
98
|
+
value = 1
|
|
99
|
+
|
|
100
|
+
^^ Section11
|
|
101
|
+
value = 11
|
|
102
|
+
|
|
103
|
+
^^^ Section111
|
|
104
|
+
value = 111
|
|
105
|
+
//^^^^ Section2104
|
|
106
|
+
value = 24
|
|
107
|
+
|
|
108
|
+
^ Section2
|
|
109
|
+
value = 2
|
|
110
|
+
`)
|
|
111
|
+
}
|
|
112
|
+
*/
|
|
113
|
+
// YINI.parse(`number = 42`)
|
|
114
|
+
/*
|
|
115
|
+
Expected JS output:
|
|
116
|
+
{
|
|
117
|
+
Section1: { value: 1, Section2: { value: 11 }},
|
|
118
|
+
Section2: { value: 2 }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
*/
|
|
122
|
+
/*
|
|
123
|
+
YINI.parse(
|
|
124
|
+
`
|
|
125
|
+
// Using numeric shorthand section markers.
|
|
126
|
+
|
|
127
|
+
@yini
|
|
128
|
+
|
|
129
|
+
// This whole line is a comment.
|
|
130
|
+
^SectionName# This part is a comment.
|
|
131
|
+
// This whole line is a comment.
|
|
132
|
+
--x=1
|
|
133
|
+
`,
|
|
134
|
+
false,
|
|
135
|
+
2,
|
|
136
|
+
)
|
|
137
|
+
*/
|
|
138
|
+
// YINI.parse(`^1 SectionName`, false, 2)
|
|
139
|
+
// const validYini = `
|
|
140
|
+
// ~ user
|
|
141
|
+
// username = 'tester two'
|
|
142
|
+
// isSysOp = YES
|
|
143
|
+
// ~~ prefs
|
|
144
|
+
// theme = "light"
|
|
145
|
+
// notifications = OFF
|
|
146
|
+
// ^1 user2
|
|
147
|
+
// ^2 prefs
|
|
148
|
+
// ^3 deepSection
|
|
149
|
+
// ^4 deeperSection
|
|
150
|
+
// key = "Level 4 section"
|
|
151
|
+
// ^5 yetDeeperSection
|
|
152
|
+
// key = "Level 5 section"
|
|
153
|
+
// item = 77
|
|
154
|
+
// ~1 user3
|
|
155
|
+
// username = 'tester three'
|
|
156
|
+
// isSysOp = NO
|
|
157
|
+
// ~~2 prefs
|
|
158
|
+
// theme = "special-dark"
|
|
159
|
+
// notifications = ON
|
|
160
|
+
// `
|
|
161
|
+
// // Act.
|
|
162
|
+
// const result = YINI.parse(validYini)
|
|
163
|
+
// debugPrint(result)
|
|
164
|
+
// const validYini = `^ App
|
|
165
|
+
// id = 32403 # The correct app id.
|
|
166
|
+
// title = "My Program"
|
|
167
|
+
// `
|
|
168
|
+
const validYini = `
|
|
169
|
+
~1 user3
|
|
170
|
+
username = 'tester three'
|
|
171
|
+
isSysOp = NO
|
|
172
|
+
|
|
173
|
+
~~2 prefs // NOT OK, bad section marker, cannot mix marker types.
|
|
174
|
+
theme = "special-dark"
|
|
175
|
+
notifications = ON
|
|
176
|
+
`;
|
|
177
|
+
YINI_1.default.parse(validYini, false, 'auto', true);
|
|
178
|
+
// YINI.parse(`
|
|
179
|
+
// ^ Section1
|
|
180
|
+
// ^^ Section2
|
|
181
|
+
// ^^^ Section3
|
|
182
|
+
// ^^^^ Section4 // Level 4.
|
|
183
|
+
// ^^^^^ Section5
|
|
184
|
+
// ^^^^^^ Section6
|
|
185
|
+
// ^^^^^^^ Section7
|
|
186
|
+
// strVar = "These section header are valid!"
|
|
187
|
+
// `)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
exports.parseMain = void 0;
|
|
7
|
+
const antlr4_1 = require("antlr4");
|
|
8
|
+
const env_1 = require("./config/env");
|
|
9
|
+
const ErrorDataHandler_1 = require("./core/ErrorDataHandler");
|
|
10
|
+
const objectBuilder_1 = require("./core/objectBuilder");
|
|
11
|
+
const YINIVisitor_1 = __importDefault(require("./core/YINIVisitor"));
|
|
12
|
+
const YiniLexer_1 = __importDefault(require("./grammar/YiniLexer"));
|
|
13
|
+
const YiniParser_1 = __importDefault(require("./grammar/YiniParser"));
|
|
14
|
+
const system_1 = require("./utils/system");
|
|
15
|
+
class MyErrorListener {
|
|
16
|
+
constructor(errorHandler) {
|
|
17
|
+
this.errors = [];
|
|
18
|
+
this.errorHandler = errorHandler;
|
|
19
|
+
}
|
|
20
|
+
syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e) {
|
|
21
|
+
(0, system_1.debugPrint)('ANTLR grammar cached an error');
|
|
22
|
+
this.errors.push(`Line ${line}:${charPositionInLine} ${msg}`);
|
|
23
|
+
const msgWhat = `Syntax error, at line: ${line}`;
|
|
24
|
+
const msgWhy = `At about column ${1 + charPositionInLine} ${msg}`;
|
|
25
|
+
this.errorHandler.pushOrBail(null, 'Syntax-Error', msgWhat, msgWhy);
|
|
26
|
+
}
|
|
27
|
+
// The following are required for the interface, but can be left empty.
|
|
28
|
+
reportAmbiguity(...args) { }
|
|
29
|
+
reportAttemptingFullContext(...args) { }
|
|
30
|
+
reportContextSensitivity(...args) { }
|
|
31
|
+
}
|
|
32
|
+
const parseMain = (yiniContent, options = {
|
|
33
|
+
isStrict: false,
|
|
34
|
+
bailSensitivityLevel: 0,
|
|
35
|
+
isIncludeMeta: false,
|
|
36
|
+
isWithDiagnostics: false,
|
|
37
|
+
isWithTiming: false,
|
|
38
|
+
}) => {
|
|
39
|
+
(0, system_1.debugPrint)();
|
|
40
|
+
(0, system_1.debugPrint)('-> Entered parseMain(..) in parseEntry');
|
|
41
|
+
(0, system_1.debugPrint)(' isStrict mode = ' + options.isStrict);
|
|
42
|
+
(0, system_1.debugPrint)('bailSensitivityLevel = ' + options.bailSensitivityLevel);
|
|
43
|
+
let persistThreshold;
|
|
44
|
+
switch (options.bailSensitivityLevel) {
|
|
45
|
+
case 0:
|
|
46
|
+
persistThreshold = '0-Ignore-Errors';
|
|
47
|
+
break;
|
|
48
|
+
case 1:
|
|
49
|
+
persistThreshold = '1-Abort-on-Errors';
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
persistThreshold = '2-Abort-Even-on-Warnings';
|
|
53
|
+
}
|
|
54
|
+
(0, env_1.isDebug)() && console.log();
|
|
55
|
+
(0, system_1.debugPrint)('=== Phase 1 ===================================================');
|
|
56
|
+
const inputStream = antlr4_1.CharStreams.fromString(yiniContent);
|
|
57
|
+
const lexer = new YiniLexer_1.default(inputStream);
|
|
58
|
+
const tokenStream = new antlr4_1.CommonTokenStream(lexer);
|
|
59
|
+
const parser = new YiniParser_1.default(tokenStream);
|
|
60
|
+
const errorHandler = new ErrorDataHandler_1.ErrorDataHandler(persistThreshold);
|
|
61
|
+
const errorListener = new MyErrorListener(errorHandler);
|
|
62
|
+
parser.removeErrorListeners(); // Removes the default console error output.
|
|
63
|
+
parser.addErrorListener(errorListener);
|
|
64
|
+
(0, system_1.debugPrint)();
|
|
65
|
+
(0, system_1.debugPrint)('--- Starting parsing... ---');
|
|
66
|
+
const parseTree = parser.yini(); // The function yini() is the start rule.
|
|
67
|
+
if (errorListener.errors.length > 0) {
|
|
68
|
+
(0, system_1.debugPrint)('*** ERROR detected ***');
|
|
69
|
+
if ((0, env_1.isDebug)()) {
|
|
70
|
+
// Handle or display syntax errors
|
|
71
|
+
console.error('Syntax errors detected:', errorListener.errors);
|
|
72
|
+
//process.exit(1)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
(0, system_1.debugPrint)('--- Parsing done. ---');
|
|
76
|
+
(0, system_1.debugPrint)('=== Ended phase 1 =============================================');
|
|
77
|
+
(0, env_1.isDebug)() && console.log();
|
|
78
|
+
(0, system_1.debugPrint)('=== Phase 2 ===================================================');
|
|
79
|
+
// const errorHandler = new ErrorDataHandler(persistThreshold)
|
|
80
|
+
const visitor = new YINIVisitor_1.default(errorHandler, options.isStrict);
|
|
81
|
+
const syntaxTreeC = visitor.visit(parseTree);
|
|
82
|
+
if ((0, env_1.isDebug)()) {
|
|
83
|
+
console.log();
|
|
84
|
+
console.log('**************************************************************************');
|
|
85
|
+
console.log('*** syntaxTreeContainer: *************************************************');
|
|
86
|
+
(0, system_1.printObject)(syntaxTreeC);
|
|
87
|
+
console.log('**************************************************************************');
|
|
88
|
+
console.log('**************************************************************************');
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
(0, system_1.debugPrint)('=== Ended phase 2 =============================================');
|
|
92
|
+
(0, env_1.isDebug)() && console.log();
|
|
93
|
+
(0, system_1.debugPrint)('=== Phase 3 ===================================================');
|
|
94
|
+
// Construct.
|
|
95
|
+
const finalJSResult = (0, objectBuilder_1.constructFinalObject)(syntaxTreeC, errorHandler);
|
|
96
|
+
(0, system_1.debugPrint)('=== Ended phase 3 =============================================');
|
|
97
|
+
(0, system_1.debugPrint)('visitor.visit(..): finalJSResult:');
|
|
98
|
+
(0, env_1.isDebug)() && console.debug(finalJSResult);
|
|
99
|
+
(0, system_1.debugPrint)();
|
|
100
|
+
if (options.isStrict) {
|
|
101
|
+
//throw Error('ERROR: Strict-mode not yet implemented')
|
|
102
|
+
errorHandler.pushOrBail(null, 'Syntax-Warning', 'WARNING: Strict-mode not yet fully implemented', '', '');
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
(0, system_1.debugPrint)('visitor.visit(..): finalJSResult:');
|
|
106
|
+
(0, env_1.isDebug)() && console.debug(finalJSResult);
|
|
107
|
+
}
|
|
108
|
+
// Construct meta data.
|
|
109
|
+
const metaData = {
|
|
110
|
+
strictMode: options.isStrict,
|
|
111
|
+
hasTerminal: syntaxTreeC._hasTerminal,
|
|
112
|
+
sections: syntaxTreeC._meta_numOfSections,
|
|
113
|
+
members: syntaxTreeC._meta_numOfMembers,
|
|
114
|
+
sectionChains: syntaxTreeC._meta_numOfChains,
|
|
115
|
+
keysParsed: null,
|
|
116
|
+
timing: {
|
|
117
|
+
totalMs: null,
|
|
118
|
+
phase1Ms: null,
|
|
119
|
+
phase2Ms: null,
|
|
120
|
+
phase3Ms: null,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
if (options.isWithDiagnostics) {
|
|
124
|
+
// Attach optional diagnostics.
|
|
125
|
+
metaData.diagnostics = {
|
|
126
|
+
bailSensitivityLevel: options.bailSensitivityLevel,
|
|
127
|
+
errors: errorHandler.getNumOfErrors(),
|
|
128
|
+
warnings: errorHandler.getNumOfWarnings(),
|
|
129
|
+
infoAndNotices: errorHandler.getNumOfInfoAndNotices(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (options.isWithTiming) {
|
|
133
|
+
// Attach optional timing data.
|
|
134
|
+
metaData.timing = {
|
|
135
|
+
totalMs: null,
|
|
136
|
+
phase1Ms: null,
|
|
137
|
+
phase2Ms: null,
|
|
138
|
+
phase3Ms: null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
(0, system_1.debugPrint)('getNumOfErrors(): ' + errorHandler.getNumOfErrors());
|
|
142
|
+
if (errorHandler.getNumOfErrors()) {
|
|
143
|
+
console.log();
|
|
144
|
+
console.log('Parsing is complete, but some problems were detected. Please see the errors above for details.');
|
|
145
|
+
console.log('Number of errors found: ' + errorHandler.getNumOfErrors());
|
|
146
|
+
}
|
|
147
|
+
if (options.isIncludeMeta) {
|
|
148
|
+
return {
|
|
149
|
+
result: finalJSResult,
|
|
150
|
+
meta: metaData,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return finalJSResult;
|
|
154
|
+
};
|
|
155
|
+
exports.parseMain = parseMain;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const system_1 = require("../utils/system");
|
|
4
|
+
const yiniHelpers_1 = require("../yiniHelpers");
|
|
5
|
+
const extractSignificantYiniLine_1 = require("./extractSignificantYiniLine");
|
|
6
|
+
/**
|
|
7
|
+
* Check and identify the section header parts via tokenizing the parts and return them as strings.
|
|
8
|
+
* @param rawHeaderLine Raw line with the section header where the header
|
|
9
|
+
* marker will be identified. E.g. does the header start with '^^^' or '^3'
|
|
10
|
+
* and then some identifier.
|
|
11
|
+
*
|
|
12
|
+
* Below, copied from YINI Specification v1.0.0 Beta 7.
|
|
13
|
+
* Form 1: Identifier of Simple Form:
|
|
14
|
+
* - Keys must be non-empty.
|
|
15
|
+
* - Keys are case-sensitive (`Title` and `title` are different).
|
|
16
|
+
* - Can only contain letters (a-z or A-Z), digits (0-9) and underscores `_`.
|
|
17
|
+
* - Must begin with a letter or an underscore `_`.
|
|
18
|
+
* - Note: Cannot contain hyphens (`-`) or periods (`.`).
|
|
19
|
+
*
|
|
20
|
+
* Form 2: Backticked Identifier:
|
|
21
|
+
* - A phrase is a name wrapped in backticks ``` ` ```.
|
|
22
|
+
* - Backticked identifiers must be on a single line and must not contain tabs or new lines unless using escaping codes, except for ordinary spaces.
|
|
23
|
+
* - Special control characters (U+0000–U+001F) must be escaped.
|
|
24
|
+
*
|
|
25
|
+
* @note Returns the parts as strings; each part needs to be analyzed separately against the contraints in the specifications.
|
|
26
|
+
* @returns An object with the identified header parts: marker characters, parsed name, and parsed level string.
|
|
27
|
+
*/
|
|
28
|
+
const extractHeaderParts = (rawLine, errorHandler = null, ctx = null) => {
|
|
29
|
+
(0, system_1.debugPrint)('-> Entered extractHeaderParts(..)');
|
|
30
|
+
rawLine = rawLine.trim();
|
|
31
|
+
const str = (0, extractSignificantYiniLine_1.extractYiniLine)(rawLine);
|
|
32
|
+
(0, system_1.debugPrint)('rawLine: >>>' + rawLine + '<<<');
|
|
33
|
+
(0, system_1.debugPrint)(' str: >>>' + str + '<<<');
|
|
34
|
+
// Edge case: empty line.
|
|
35
|
+
if (!str) {
|
|
36
|
+
errorHandler.pushOrBail(ctx, 'Internal-Error', 'Received blank argument in extractHeaderParts(..)', 'Sorry, an unintended internal error happened.');
|
|
37
|
+
}
|
|
38
|
+
let pos = 0;
|
|
39
|
+
const len = str.length;
|
|
40
|
+
let markerCharsPart = '';
|
|
41
|
+
let numberPart = '';
|
|
42
|
+
let sectionNamePart = '';
|
|
43
|
+
let isBacktickedName = false;
|
|
44
|
+
// 1. Skip leading whitespace.
|
|
45
|
+
while (pos < len && (str[pos] === ' ' || str[pos] === '\t'))
|
|
46
|
+
pos++;
|
|
47
|
+
// 2. Collect marker(s): ^, ~, §, €.
|
|
48
|
+
while (pos < len && (0, yiniHelpers_1.isMarkerCharacter)(str[pos])) {
|
|
49
|
+
markerCharsPart += str[pos];
|
|
50
|
+
pos++;
|
|
51
|
+
}
|
|
52
|
+
// 3. Numeric part (for numeric shorthand; only if single marker found).
|
|
53
|
+
if (markerCharsPart.length === 1 &&
|
|
54
|
+
pos < len &&
|
|
55
|
+
str[pos] >= '1' &&
|
|
56
|
+
str[pos] <= '9') {
|
|
57
|
+
// Start collecting number part.
|
|
58
|
+
while (pos < len && str[pos] >= '0' && str[pos] <= '9') {
|
|
59
|
+
numberPart += str[pos];
|
|
60
|
+
pos++;
|
|
61
|
+
}
|
|
62
|
+
markerCharsPart += numberPart; // E.g., "^7".
|
|
63
|
+
}
|
|
64
|
+
// 4. Skip whitespace between marker and section name.
|
|
65
|
+
while (pos < len && (str[pos] === ' ' || str[pos] === '\t'))
|
|
66
|
+
pos++;
|
|
67
|
+
// 5. Collect section name (identifier or backticked).
|
|
68
|
+
if (pos < len && str[pos] === '`') {
|
|
69
|
+
// Backticked identifier.
|
|
70
|
+
let start = pos;
|
|
71
|
+
pos++; // Skip initial backtick.
|
|
72
|
+
while (pos < len && str[pos] !== '`')
|
|
73
|
+
pos++;
|
|
74
|
+
pos++; // Include the closing backtick (if found).
|
|
75
|
+
sectionNamePart = str.slice(start, pos).trim();
|
|
76
|
+
isBacktickedName = true;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Non-backticked: take the rest of the line, trim off any trailing comments, etc.
|
|
80
|
+
sectionNamePart = str.slice(pos).trim();
|
|
81
|
+
// Optionally, strip trailing comments or newlines here if needed.
|
|
82
|
+
}
|
|
83
|
+
if (isBacktickedName) {
|
|
84
|
+
(0, system_1.debugPrint)('Backticed sectionNamePart: ' + sectionNamePart);
|
|
85
|
+
// sectionNamePart = trimBackticks(sectionNamePart)
|
|
86
|
+
}
|
|
87
|
+
(0, system_1.debugPrint)();
|
|
88
|
+
(0, system_1.debugPrint)('------');
|
|
89
|
+
(0, system_1.debugPrint)('<- About to leave extractHeaderParts(..)');
|
|
90
|
+
(0, system_1.debugPrint)();
|
|
91
|
+
(0, system_1.debugPrint)(' markerCharsPart: >>>' + markerCharsPart + '<<<');
|
|
92
|
+
(0, system_1.debugPrint)(' sectionNamePart: >>>' + sectionNamePart + '<<<');
|
|
93
|
+
(0, system_1.debugPrint)(' numberPart: >>>' + numberPart + '<<<');
|
|
94
|
+
(0, system_1.debugPrint)(' isBacktickedName: ' + isBacktickedName);
|
|
95
|
+
(0, system_1.debugPrint)();
|
|
96
|
+
return {
|
|
97
|
+
strMarkerChars: markerCharsPart,
|
|
98
|
+
strSectionName: sectionNamePart,
|
|
99
|
+
strNumberPart: numberPart,
|
|
100
|
+
isBacktickedName,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
exports.default = extractHeaderParts;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractYiniLine = void 0;
|
|
4
|
+
const env_1 = require("../config/env");
|
|
5
|
+
const string_1 = require("../utils/string");
|
|
6
|
+
const system_1 = require("../utils/system");
|
|
7
|
+
const yiniHelpers_1 = require("../yiniHelpers");
|
|
8
|
+
/**
|
|
9
|
+
* Extract significant YINI line from YINI content (that may be surrounded by comments.).
|
|
10
|
+
* @param rawYiniContent For example:
|
|
11
|
+
* // This whole line is a comment.
|
|
12
|
+
* ^SectionName# This part is a comment.
|
|
13
|
+
* // This whole line is a comment.
|
|
14
|
+
* @returns Will filter out any comments (before or after) and return only one single significant YINI line.
|
|
15
|
+
*/
|
|
16
|
+
const extractYiniLine = (rawYiniContent) => {
|
|
17
|
+
(0, system_1.debugPrint)('-> Entered extractSignificantYiniCode(..)');
|
|
18
|
+
const significantLines = [];
|
|
19
|
+
let resultLine = '';
|
|
20
|
+
(0, system_1.debugPrint)('rawYiniContent: >>>' + rawYiniContent + '<<<');
|
|
21
|
+
const contentLines = (0, string_1.splitLines)(rawYiniContent);
|
|
22
|
+
if ((0, env_1.isDebug)()) {
|
|
23
|
+
console.log(`contentLines: (len: ${contentLines.length})`);
|
|
24
|
+
(0, system_1.printObject)(contentLines);
|
|
25
|
+
}
|
|
26
|
+
// contentLines.forEach((row: string) => {
|
|
27
|
+
for (const line of contentLines) {
|
|
28
|
+
let row = line;
|
|
29
|
+
(0, system_1.debugPrint)('---');
|
|
30
|
+
// debugPrint('row (a): >>>' + row + '<<<')
|
|
31
|
+
// row = stripNLAndAfter(row)
|
|
32
|
+
(0, system_1.debugPrint)('row (b): >>>' + row + '<<<');
|
|
33
|
+
row = (0, yiniHelpers_1.stripCommentsAndAfter)(row);
|
|
34
|
+
(0, system_1.debugPrint)('row (c): >>>' + row + '<<<');
|
|
35
|
+
row = row.trim();
|
|
36
|
+
(0, system_1.debugPrint)('row (d): >>>' + row + '<<<');
|
|
37
|
+
if (row) {
|
|
38
|
+
(0, system_1.debugPrint)('Found some content in split row (non-comments).');
|
|
39
|
+
(0, system_1.debugPrint)('Split row: >>>' + row + '<<<');
|
|
40
|
+
// Use this as input in line.
|
|
41
|
+
// line = row
|
|
42
|
+
significantLines.push(row);
|
|
43
|
+
// break
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
(0, system_1.debugPrint)('--- End: parse line from section content-----------------');
|
|
47
|
+
(0, system_1.debugPrint)();
|
|
48
|
+
switch (significantLines.length) {
|
|
49
|
+
case 0:
|
|
50
|
+
resultLine = '';
|
|
51
|
+
break;
|
|
52
|
+
case 1:
|
|
53
|
+
(0, system_1.debugPrint)('Did only find one significant lines in rawYiniContent, OK');
|
|
54
|
+
resultLine = significantLines[0];
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
(0, system_1.debugPrint)('(!) Did find several significant lines in rawYiniContent! - Maybe internal error...');
|
|
58
|
+
// throw new Error(
|
|
59
|
+
// 'Internal error: Detected several row lines in rawYiniContent: >>>' +
|
|
60
|
+
// rawYiniContent +
|
|
61
|
+
// '<<<',
|
|
62
|
+
// )
|
|
63
|
+
}
|
|
64
|
+
(0, system_1.debugPrint)('<- About to leave extractSignificantYiniCode(..):');
|
|
65
|
+
(0, system_1.debugPrint)('resultLine: >>>' + resultLine + '<<<');
|
|
66
|
+
return resultLine;
|
|
67
|
+
};
|
|
68
|
+
exports.extractYiniLine = extractYiniLine;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const system_1 = require("../utils/system");
|
|
4
|
+
/**
|
|
5
|
+
* Extract boolean literal.
|
|
6
|
+
*/
|
|
7
|
+
const parseBooleanLiteral = (txt) => {
|
|
8
|
+
(0, system_1.debugPrint)('-> Entered parseBooleanLiteral(..)');
|
|
9
|
+
const value = !!(txt === 'true' || txt === 'yes' || txt === 'on');
|
|
10
|
+
return value;
|
|
11
|
+
};
|
|
12
|
+
exports.default = parseBooleanLiteral;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const system_1 = require("../utils/system");
|
|
4
|
+
const parseNullLiteral = (txt) => {
|
|
5
|
+
(0, system_1.debugPrint)('-> Entered parseNullLiteral(..)');
|
|
6
|
+
if (txt.toLowerCase() !== 'null') {
|
|
7
|
+
throw Error('Syntax Error: Unexpected token or character; expected `Null` literal (case-insensitive)');
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
};
|
|
11
|
+
exports.default = parseNullLiteral;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const system_1 = require("../utils/system");
|
|
4
|
+
const parseNumberLiteral = (txt) => {
|
|
5
|
+
(0, system_1.debugPrint)('-> Entered parseNumberLiteral(..), txt: ' + txt);
|
|
6
|
+
if (/^0[xX]|#/.test(txt)) {
|
|
7
|
+
// Prefix: 0x, 0X, #
|
|
8
|
+
(0, system_1.debugPrint)('* Identified as a hex number');
|
|
9
|
+
return {
|
|
10
|
+
type: 'Number-Integer',
|
|
11
|
+
// value: parseInt(txt.replace('#', '0x'), 16),
|
|
12
|
+
value: parseInt(txt.replace(/^0[xX]|#/, ''), 16),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (/^0[bB]|%/.test(txt)) {
|
|
16
|
+
// Prefix: 0b, 0B, %
|
|
17
|
+
(0, system_1.debugPrint)('* Identified as a bin number');
|
|
18
|
+
return {
|
|
19
|
+
type: 'Number-Integer',
|
|
20
|
+
value: parseInt(txt.replace(/^0[bB]|%/, ''), 2),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (/^0[oO]/.test(txt)) {
|
|
24
|
+
// Prefix: 0o, 0O
|
|
25
|
+
(0, system_1.debugPrint)('* Identified as a ord number');
|
|
26
|
+
return {
|
|
27
|
+
type: 'Number-Integer',
|
|
28
|
+
value: parseInt(txt.replace(/^0[oO]/, ''), 8),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (/^0[zZ]/.test(txt)) {
|
|
32
|
+
// Prefix: 0z, 0Z
|
|
33
|
+
(0, system_1.debugPrint)('* Identified as a duodecimal number');
|
|
34
|
+
return {
|
|
35
|
+
type: 'Number-Integer',
|
|
36
|
+
value: parseInt(txt.replace(/^0[zZ]/, ''), 12),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// In a regex literal the dot must be escaped (\.) to match a literal '.'
|
|
40
|
+
if (/\./.test(txt)) {
|
|
41
|
+
(0, system_1.debugPrint)('* Identified as a float number');
|
|
42
|
+
return { type: 'Number-Float', value: parseFloat(txt) };
|
|
43
|
+
}
|
|
44
|
+
// TODO: Depending, on mode, below continue or break on error
|
|
45
|
+
//console.error('Error: Failed to parse number value: ' + txt)
|
|
46
|
+
(0, system_1.debugPrint)('* Identified as a int number');
|
|
47
|
+
return { type: 'Number-Integer', value: parseInt(txt) };
|
|
48
|
+
};
|
|
49
|
+
exports.default = parseNumberLiteral;
|