xcdn 1.0.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/LICENSE +21 -0
- package/README.md +139 -0
- package/package.json +35 -0
- package/src/ast.js +409 -0
- package/src/error.js +106 -0
- package/src/index.js +36 -0
- package/src/lexer.js +475 -0
- package/src/parser.js +427 -0
- package/src/serializer.js +392 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gioele Stefano Luca Fierro
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# > xCDN_ (JavaScript)
|
|
2
|
+
|
|
3
|
+
A complete JavaScript library to parse, serialize and deserialize **> xCDN_ — eXtensible Cognitive Data Notation**.
|
|
4
|
+
|
|
5
|
+
> **What is > xCDN_?**
|
|
6
|
+
> xCDN_ is a human-first, machine-optimized data notation with native types, tags and annotations.
|
|
7
|
+
> It supports comments, trailing commas, unquoted keys and multi-line strings.
|
|
8
|
+
> You can read more about this notation in the [> xCDN_ repository](https://github.com/gslf/xCDN).
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Full streaming document model (one or more top-level values)
|
|
13
|
+
- Optional **prolog** (`$schema: "..."`, ...)
|
|
14
|
+
- Objects, arrays and scalars
|
|
15
|
+
- Native types: `Decimal` (`d"..."`), `UUID` (`u"..."`), `DateTime` (`t"..."` RFC3339),
|
|
16
|
+
`Duration` (`r"..."` ISO8601), `Bytes` (`b"..."` Base64)
|
|
17
|
+
- `#tags` and `@annotations(args?)` that decorate any value
|
|
18
|
+
- Comments: `//` and `/* ... */`
|
|
19
|
+
- Trailing commas and unquoted keys
|
|
20
|
+
- Pretty or compact serialization
|
|
21
|
+
- Zero dependencies
|
|
22
|
+
|
|
23
|
+
## Example
|
|
24
|
+
|
|
25
|
+
```xcdn
|
|
26
|
+
$schema: "https://gslf.github.io/xCDN/schemas/v1/meta.xcdn",
|
|
27
|
+
|
|
28
|
+
server_config: {
|
|
29
|
+
host: "localhost",
|
|
30
|
+
// Unquoted keys & trailing commas? Yes.
|
|
31
|
+
ports: [8080, 9090,],
|
|
32
|
+
|
|
33
|
+
// Native Decimals & ISO8601 Duration
|
|
34
|
+
timeout: r"PT30S",
|
|
35
|
+
max_cost: d"19.99",
|
|
36
|
+
|
|
37
|
+
// Semantic Tagging
|
|
38
|
+
admin: #user {
|
|
39
|
+
id: u"550e8400-e29b-41d4-a716-446655440000",
|
|
40
|
+
role: "superuser"
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Binary data handling
|
|
44
|
+
icon: @mime("image/png") b"iVBORw0KGgoAAAANSUhEUgAAAAUA...",
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install xcdn
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import xcdn from 'xcdn';
|
|
58
|
+
|
|
59
|
+
// Parse an xCDN string
|
|
60
|
+
const doc = xcdn.parse(`
|
|
61
|
+
name: "Alice",
|
|
62
|
+
age: 30,
|
|
63
|
+
active: true,
|
|
64
|
+
`);
|
|
65
|
+
|
|
66
|
+
// Pretty-print
|
|
67
|
+
console.log(xcdn.stringify(doc));
|
|
68
|
+
|
|
69
|
+
// Compact output
|
|
70
|
+
console.log(xcdn.stringifyCompact(doc));
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Named imports
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import { parseStr, toStringPretty, toStringCompact } from 'xcdn';
|
|
77
|
+
|
|
78
|
+
const doc = parseStr(source);
|
|
79
|
+
const pretty = toStringPretty(doc);
|
|
80
|
+
const compact = toStringCompact(doc);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Accessing fields
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
import { parseStr } from 'xcdn';
|
|
87
|
+
|
|
88
|
+
const doc = parseStr(`
|
|
89
|
+
user: {
|
|
90
|
+
name: "Mario Rossi",
|
|
91
|
+
age: 30,
|
|
92
|
+
roles: [#admin "administrator", #user "standard"],
|
|
93
|
+
},
|
|
94
|
+
session_id: u"550e8400-e29b-41d4-a716-446655440000",
|
|
95
|
+
balance: d"1234.56",
|
|
96
|
+
created_at: t"2025-01-15T10:30:00Z",
|
|
97
|
+
`);
|
|
98
|
+
|
|
99
|
+
const root = doc.values[0].value;
|
|
100
|
+
|
|
101
|
+
// Access nested values
|
|
102
|
+
root.get('user').value.get('name').value.value; // "Mario Rossi"
|
|
103
|
+
|
|
104
|
+
// Check keys
|
|
105
|
+
root.has('user'); // true
|
|
106
|
+
root.keys(); // ["user", "session_id", "balance", "created_at"]
|
|
107
|
+
|
|
108
|
+
// Iterate decorated items
|
|
109
|
+
const roles = root.get('user').value.get('roles').value;
|
|
110
|
+
for (const role of roles) {
|
|
111
|
+
console.log(role.tags[0].name, role.value.value);
|
|
112
|
+
// "admin" "administrator"
|
|
113
|
+
// "user" "standard"
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Sub-module imports
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
import { Parser } from 'xcdn/parser';
|
|
121
|
+
import { Serializer, Format } from 'xcdn/serializer';
|
|
122
|
+
import { XObject, XArray, Node, XString } from 'xcdn/ast';
|
|
123
|
+
import { XCDNError } from 'xcdn/error';
|
|
124
|
+
import { Lexer } from 'xcdn/lexer';
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
npm test
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT, see [LICENSE](LICENSE).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
#### This is a :/# GSLF project.
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xcdn",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "xCDN parser and serializer for JavaScript",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./ast": "./src/ast.js",
|
|
10
|
+
"./error": "./src/error.js",
|
|
11
|
+
"./lexer": "./src/lexer.js",
|
|
12
|
+
"./parser": "./src/parser.js",
|
|
13
|
+
"./serializer": "./src/serializer.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test tests/"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"xcdn",
|
|
20
|
+
"parser",
|
|
21
|
+
"serializer",
|
|
22
|
+
"data-format"
|
|
23
|
+
],
|
|
24
|
+
"author": "GioeleSLFierro",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/gslf/xCDN-JavaScript.git"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"src/",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT"
|
|
35
|
+
}
|
package/src/ast.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xCDN AST Module
|
|
3
|
+
* Data structures for the xCDN format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Main xCDN document
|
|
8
|
+
*/
|
|
9
|
+
export class Document {
|
|
10
|
+
/**
|
|
11
|
+
* @param {Directive[]} prolog - Initial directives ($schema, etc.)
|
|
12
|
+
* @param {Node[]} values - Top-level values
|
|
13
|
+
*/
|
|
14
|
+
constructor(prolog = [], values = []) {
|
|
15
|
+
this.prolog = prolog;
|
|
16
|
+
this.values = values;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Dict-like access
|
|
21
|
+
* @param {number|string} key
|
|
22
|
+
*/
|
|
23
|
+
get(key) {
|
|
24
|
+
if (typeof key === 'number') {
|
|
25
|
+
return this.values[key];
|
|
26
|
+
}
|
|
27
|
+
// Access the first object
|
|
28
|
+
if (this.values.length > 0 && this.values[0].value instanceof XObject) {
|
|
29
|
+
return this.values[0].value.get(key);
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Set dict-like
|
|
36
|
+
* @param {string} key
|
|
37
|
+
* @param {*} value
|
|
38
|
+
*/
|
|
39
|
+
set(key, value) {
|
|
40
|
+
if (this.values.length > 0 && this.values[0].value instanceof XObject) {
|
|
41
|
+
this.values[0].value.set(key, value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if key exists
|
|
47
|
+
* @param {string} key
|
|
48
|
+
*/
|
|
49
|
+
has(key) {
|
|
50
|
+
if (this.values.length > 0 && this.values[0].value instanceof XObject) {
|
|
51
|
+
return this.values[0].value.has(key);
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Directive ($name: value)
|
|
59
|
+
*/
|
|
60
|
+
export class Directive {
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} name - Directive name (without $)
|
|
63
|
+
* @param {*} value - Directive value
|
|
64
|
+
*/
|
|
65
|
+
constructor(name, value) {
|
|
66
|
+
this.name = name;
|
|
67
|
+
this.value = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Node decorated with tags and annotations
|
|
73
|
+
*/
|
|
74
|
+
export class Node {
|
|
75
|
+
/**
|
|
76
|
+
* @param {Tag[]} tags - List of tags (#tag)
|
|
77
|
+
* @param {Annotation[]} annotations - List of annotations (@anno)
|
|
78
|
+
* @param {*} value - Actual value
|
|
79
|
+
*/
|
|
80
|
+
constructor(tags = [], annotations = [], value = null) {
|
|
81
|
+
this.tags = tags;
|
|
82
|
+
this.annotations = annotations;
|
|
83
|
+
this.value = value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Dict-like methods delegated to the value
|
|
87
|
+
get(key, defaultValue = undefined) {
|
|
88
|
+
if (this.value && typeof this.value.get === 'function') {
|
|
89
|
+
return this.value.get(key, defaultValue);
|
|
90
|
+
}
|
|
91
|
+
return defaultValue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
set(key, value) {
|
|
95
|
+
if (this.value && typeof this.value.set === 'function') {
|
|
96
|
+
this.value.set(key, value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
has(key) {
|
|
101
|
+
if (this.value && typeof this.value.has === 'function') {
|
|
102
|
+
return this.value.has(key);
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
keys() {
|
|
108
|
+
if (this.value instanceof XObject) {
|
|
109
|
+
return this.value.keys();
|
|
110
|
+
}
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
valuesIter() {
|
|
115
|
+
if (this.value instanceof XObject) {
|
|
116
|
+
return this.value.valuesIter();
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
items() {
|
|
122
|
+
if (this.value instanceof XObject) {
|
|
123
|
+
return this.value.items();
|
|
124
|
+
}
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
append(value) {
|
|
129
|
+
if (this.value instanceof XArray) {
|
|
130
|
+
this.value.append(value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get length() {
|
|
135
|
+
if (this.value && typeof this.value.length !== 'undefined') {
|
|
136
|
+
return this.value.length;
|
|
137
|
+
}
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
[Symbol.iterator]() {
|
|
142
|
+
if (this.value && typeof this.value[Symbol.iterator] === 'function') {
|
|
143
|
+
return this.value[Symbol.iterator]();
|
|
144
|
+
}
|
|
145
|
+
return [][Symbol.iterator]();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Tag (#tag)
|
|
151
|
+
*/
|
|
152
|
+
export class Tag {
|
|
153
|
+
/**
|
|
154
|
+
* @param {string} name - Tag name
|
|
155
|
+
*/
|
|
156
|
+
constructor(name) {
|
|
157
|
+
this.name = name;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Annotation (@name(args...))
|
|
163
|
+
*/
|
|
164
|
+
export class Annotation {
|
|
165
|
+
/**
|
|
166
|
+
* @param {string} name - Annotation name
|
|
167
|
+
* @param {*[]} args - Optional arguments
|
|
168
|
+
*/
|
|
169
|
+
constructor(name, args = []) {
|
|
170
|
+
this.name = name;
|
|
171
|
+
this.args = args;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============== VALUE TYPES ==============
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Base class for value types
|
|
179
|
+
*/
|
|
180
|
+
export class ValueType {
|
|
181
|
+
constructor() {
|
|
182
|
+
if (new.target === ValueType) {
|
|
183
|
+
throw new Error('ValueType is abstract');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Null value
|
|
190
|
+
*/
|
|
191
|
+
export class Null extends ValueType {
|
|
192
|
+
constructor() {
|
|
193
|
+
super();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Boolean value
|
|
199
|
+
*/
|
|
200
|
+
export class Bool extends ValueType {
|
|
201
|
+
/**
|
|
202
|
+
* @param {boolean} value
|
|
203
|
+
*/
|
|
204
|
+
constructor(value) {
|
|
205
|
+
super();
|
|
206
|
+
this.value = value;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Integer value
|
|
212
|
+
*/
|
|
213
|
+
export class Int extends ValueType {
|
|
214
|
+
/**
|
|
215
|
+
* @param {number|bigint} value
|
|
216
|
+
*/
|
|
217
|
+
constructor(value) {
|
|
218
|
+
super();
|
|
219
|
+
this.value = typeof value === 'bigint' ? value : BigInt(Math.trunc(value));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Float value
|
|
225
|
+
*/
|
|
226
|
+
export class Float extends ValueType {
|
|
227
|
+
/**
|
|
228
|
+
* @param {number} value
|
|
229
|
+
*/
|
|
230
|
+
constructor(value) {
|
|
231
|
+
super();
|
|
232
|
+
this.value = value;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Decimal value (d"...")
|
|
238
|
+
*/
|
|
239
|
+
export class DecimalValue extends ValueType {
|
|
240
|
+
/**
|
|
241
|
+
* @param {string} value - String representation of the decimal
|
|
242
|
+
*/
|
|
243
|
+
constructor(value) {
|
|
244
|
+
super();
|
|
245
|
+
this.value = value; // Keep as string for precision
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* String value
|
|
251
|
+
*/
|
|
252
|
+
export class XString extends ValueType {
|
|
253
|
+
/**
|
|
254
|
+
* @param {string} value
|
|
255
|
+
*/
|
|
256
|
+
constructor(value) {
|
|
257
|
+
super();
|
|
258
|
+
this.value = value;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Bytes value (b"..." base64)
|
|
264
|
+
*/
|
|
265
|
+
export class Bytes extends ValueType {
|
|
266
|
+
/**
|
|
267
|
+
* @param {Uint8Array} value - Decoded bytes
|
|
268
|
+
*/
|
|
269
|
+
constructor(value) {
|
|
270
|
+
super();
|
|
271
|
+
this.value = value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Datetime value (t"...")
|
|
277
|
+
*/
|
|
278
|
+
export class DateTime extends ValueType {
|
|
279
|
+
/**
|
|
280
|
+
* @param {Date} value
|
|
281
|
+
*/
|
|
282
|
+
constructor(value) {
|
|
283
|
+
super();
|
|
284
|
+
this.value = value;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Duration value (r"...")
|
|
290
|
+
*/
|
|
291
|
+
export class Duration extends ValueType {
|
|
292
|
+
/**
|
|
293
|
+
* @param {string} value - ISO8601 duration as string
|
|
294
|
+
*/
|
|
295
|
+
constructor(value) {
|
|
296
|
+
super();
|
|
297
|
+
this.value = value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* UUID value (u"...")
|
|
303
|
+
*/
|
|
304
|
+
export class Uuid extends ValueType {
|
|
305
|
+
/**
|
|
306
|
+
* @param {string} value - UUID as string
|
|
307
|
+
*/
|
|
308
|
+
constructor(value) {
|
|
309
|
+
super();
|
|
310
|
+
this.value = value;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* xCDN Array
|
|
316
|
+
*/
|
|
317
|
+
export class XArray extends ValueType {
|
|
318
|
+
/**
|
|
319
|
+
* @param {Node[]} value - List of nodes
|
|
320
|
+
*/
|
|
321
|
+
constructor(value = []) {
|
|
322
|
+
super();
|
|
323
|
+
this.value = value;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
get(index) {
|
|
327
|
+
return this.value[index];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
set(index, val) {
|
|
331
|
+
this.value[index] = val;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
get length() {
|
|
335
|
+
return this.value.length;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
append(val) {
|
|
339
|
+
// Wrap in Node if not already
|
|
340
|
+
if (val instanceof Node) {
|
|
341
|
+
this.value.push(val);
|
|
342
|
+
} else {
|
|
343
|
+
this.value.push(new Node([], [], val));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
[Symbol.iterator]() {
|
|
348
|
+
return this.value[Symbol.iterator]();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* xCDN Object
|
|
354
|
+
*/
|
|
355
|
+
export class XObject extends ValueType {
|
|
356
|
+
/**
|
|
357
|
+
* @param {Map<string, Node>|Object} value - Key-value map
|
|
358
|
+
*/
|
|
359
|
+
constructor(value = new Map()) {
|
|
360
|
+
super();
|
|
361
|
+
if (value instanceof Map) {
|
|
362
|
+
this.value = value;
|
|
363
|
+
} else {
|
|
364
|
+
this.value = new Map(Object.entries(value));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
get(key, defaultValue = undefined) {
|
|
369
|
+
return this.value.has(key) ? this.value.get(key) : defaultValue;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
set(key, val) {
|
|
373
|
+
// Wrap in Node if not already
|
|
374
|
+
if (val instanceof Node) {
|
|
375
|
+
this.value.set(key, val);
|
|
376
|
+
} else {
|
|
377
|
+
this.value.set(key, new Node([], [], val));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
has(key) {
|
|
382
|
+
return this.value.has(key);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
get length() {
|
|
386
|
+
return this.value.size;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
keys() {
|
|
390
|
+
return Array.from(this.value.keys());
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
valuesIter() {
|
|
394
|
+
return Array.from(this.value.values());
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
items() {
|
|
398
|
+
return Array.from(this.value.entries());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
[Symbol.iterator]() {
|
|
402
|
+
return this.value.keys()[Symbol.iterator]();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Export aliases for compatibility
|
|
407
|
+
export { XString as String };
|
|
408
|
+
export { XArray as Array };
|
|
409
|
+
export { XObject as Object };
|
package/src/error.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xCDN Error Module
|
|
3
|
+
* Error handling for the xCDN parser
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents a position in the source code
|
|
8
|
+
*/
|
|
9
|
+
export class Span {
|
|
10
|
+
/**
|
|
11
|
+
* @param {number} offset - Absolute byte offset
|
|
12
|
+
* @param {number} line - Line number (1-indexed)
|
|
13
|
+
* @param {number} column - Column number (1-indexed)
|
|
14
|
+
*/
|
|
15
|
+
constructor(offset, line, column) {
|
|
16
|
+
this.offset = offset;
|
|
17
|
+
this.line = line;
|
|
18
|
+
this.column = column;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
toString() {
|
|
22
|
+
return `${this.line}:${this.column}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
clone() {
|
|
26
|
+
return new Span(this.offset, this.line, this.column);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Possible error types
|
|
32
|
+
*/
|
|
33
|
+
export const ErrorKind = {
|
|
34
|
+
Eof: 'Eof',
|
|
35
|
+
InvalidToken: 'InvalidToken',
|
|
36
|
+
Expected: 'Expected',
|
|
37
|
+
InvalidEscape: 'InvalidEscape',
|
|
38
|
+
InvalidNumber: 'InvalidNumber',
|
|
39
|
+
InvalidDecimal: 'InvalidDecimal',
|
|
40
|
+
InvalidDateTime: 'InvalidDateTime',
|
|
41
|
+
InvalidDuration: 'InvalidDuration',
|
|
42
|
+
InvalidUuid: 'InvalidUuid',
|
|
43
|
+
InvalidBase64: 'InvalidBase64',
|
|
44
|
+
Message: 'Message',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* xCDN error with position information
|
|
49
|
+
*/
|
|
50
|
+
export class XCDNError extends Error {
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} kind - Error type (from ErrorKind)
|
|
53
|
+
* @param {Span} span - Error position
|
|
54
|
+
* @param {string} [context] - Additional context
|
|
55
|
+
*/
|
|
56
|
+
constructor(kind, span, context = null) {
|
|
57
|
+
const message = XCDNError.formatMessage(kind, context, span);
|
|
58
|
+
super(message);
|
|
59
|
+
this.name = 'XCDNError';
|
|
60
|
+
this.kind = kind;
|
|
61
|
+
this.span = span;
|
|
62
|
+
this.context = context;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static formatMessage(kind, context, span) {
|
|
66
|
+
let msg;
|
|
67
|
+
switch (kind) {
|
|
68
|
+
case ErrorKind.Eof:
|
|
69
|
+
msg = 'Unexpected end of input';
|
|
70
|
+
break;
|
|
71
|
+
case ErrorKind.InvalidToken:
|
|
72
|
+
msg = `Invalid token: ${context}`;
|
|
73
|
+
break;
|
|
74
|
+
case ErrorKind.Expected:
|
|
75
|
+
msg = context; // "Expected X, found Y"
|
|
76
|
+
break;
|
|
77
|
+
case ErrorKind.InvalidEscape:
|
|
78
|
+
msg = 'Invalid escape sequence';
|
|
79
|
+
break;
|
|
80
|
+
case ErrorKind.InvalidNumber:
|
|
81
|
+
msg = 'Invalid number format';
|
|
82
|
+
break;
|
|
83
|
+
case ErrorKind.InvalidDecimal:
|
|
84
|
+
msg = `Invalid decimal value: ${context}`;
|
|
85
|
+
break;
|
|
86
|
+
case ErrorKind.InvalidDateTime:
|
|
87
|
+
msg = `Invalid datetime value: ${context}`;
|
|
88
|
+
break;
|
|
89
|
+
case ErrorKind.InvalidDuration:
|
|
90
|
+
msg = `Invalid duration value: ${context}`;
|
|
91
|
+
break;
|
|
92
|
+
case ErrorKind.InvalidUuid:
|
|
93
|
+
msg = `Invalid UUID value: ${context}`;
|
|
94
|
+
break;
|
|
95
|
+
case ErrorKind.InvalidBase64:
|
|
96
|
+
msg = `Invalid base64 value: ${context}`;
|
|
97
|
+
break;
|
|
98
|
+
case ErrorKind.Message:
|
|
99
|
+
msg = context;
|
|
100
|
+
break;
|
|
101
|
+
default:
|
|
102
|
+
msg = context || 'Unknown error';
|
|
103
|
+
}
|
|
104
|
+
return `${msg} at ${span.toString()}`;
|
|
105
|
+
}
|
|
106
|
+
}
|