cddl 0.11.0 → 0.12.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/build/parser.d.ts CHANGED
@@ -6,6 +6,7 @@ export default class Parser {
6
6
  l: Lexer;
7
7
  curToken: Token;
8
8
  peekToken: Token;
9
+ peekBelowToken: Token;
9
10
  constructor(filePath: string);
10
11
  private nextToken;
11
12
  private parseAssignments;
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,YAAY,CAAA;AAE9B,OAAO,EAAE,KAAK,EAAU,MAAM,aAAa,CAAC;AAG5C,OAAO,EAE2C,UAAU,EAE3D,MAAM,UAAU,CAAA;AAoBjB,MAAM,CAAC,OAAO,OAAO,MAAM;;IAEvB,CAAC,EAAE,KAAK,CAAC;IAET,QAAQ,EAAE,KAAK,CAAa;IAC5B,SAAS,EAAE,KAAK,CAAa;gBAEhB,QAAQ,EAAE,MAAM;IAQ7B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,gBAAgB;IA2CxB,OAAO,CAAC,oBAAoB;IAqb5B,OAAO,CAAC,wBAAwB;IAahC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,iBAAiB;IAqJzB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,kBAAkB;IAuD1B,OAAO,CAAC,gBAAgB;IA4DxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB,KAAK;IAaL,OAAO,CAAC,WAAW;CAKtB"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,YAAY,CAAA;AAE9B,OAAO,EAAE,KAAK,EAAU,MAAM,aAAa,CAAC;AAG5C,OAAO,EAE2C,UAAU,EAE3D,MAAM,UAAU,CAAA;AAoBjB,MAAM,CAAC,OAAO,OAAO,MAAM;;IAEvB,CAAC,EAAE,KAAK,CAAC;IAET,QAAQ,EAAE,KAAK,CAAa;IAC5B,SAAS,EAAE,KAAK,CAAa;IAC7B,cAAc,EAAE,KAAK,CAAa;gBAErB,QAAQ,EAAE,MAAM;IAS7B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,gBAAgB;IA2CxB,OAAO,CAAC,oBAAoB;IA8d5B,OAAO,CAAC,wBAAwB;IAahC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,iBAAiB;IAqKzB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,kBAAkB;IAuD1B,OAAO,CAAC,gBAAgB;IA4DxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB,KAAK;IAaL,OAAO,CAAC,WAAW;CAKtB"}
package/build/parser.js CHANGED
@@ -26,15 +26,18 @@ export default class Parser {
26
26
  l;
27
27
  curToken = NIL_TOKEN;
28
28
  peekToken = NIL_TOKEN;
29
+ peekBelowToken = NIL_TOKEN;
29
30
  constructor(filePath) {
30
31
  this.#filePath = filePath;
31
32
  this.l = new Lexer(fs.readFileSync(filePath, 'utf-8'));
32
33
  this.nextToken();
33
34
  this.nextToken();
35
+ this.nextToken();
34
36
  }
35
37
  nextToken() {
36
38
  this.curToken = this.peekToken;
37
- this.peekToken = this.l.nextToken();
39
+ this.peekToken = this.peekBelowToken;
40
+ this.peekBelowToken = this.l.nextToken();
38
41
  return true;
39
42
  }
40
43
  parseAssignments() {
@@ -110,7 +113,10 @@ export default class Parser {
110
113
  * attireBlock
111
114
  * )
112
115
  */
113
- if (closingTokens.length && this.peekToken.Type === Tokens.SLASH) {
116
+ if (closingTokens.includes(Tokens.RPAREN) &&
117
+ this.peekToken.Type === Tokens.SLASH &&
118
+ this.peekBelowToken.Type !== Tokens.SLASH &&
119
+ !(this.curToken.Type === Tokens.SLASH && this.peekToken.Type === Tokens.SLASH)) {
114
120
  const propertyType = [];
115
121
  while (!closingTokens.includes(this.curToken.Type)) {
116
122
  propertyType.push(...this.parsePropertyTypes());
@@ -123,6 +129,9 @@ export default class Parser {
123
129
  this.nextToken();
124
130
  }
125
131
  }
132
+ if (this.curToken.Type === Tokens.RPAREN) {
133
+ this.nextToken();
134
+ }
126
135
  if (groupName) {
127
136
  const variable = {
128
137
  Type: 'variable',
@@ -163,6 +172,22 @@ export default class Parser {
163
172
  return [prop];
164
173
  }
165
174
  while (!closingTokens.includes(this.curToken.Type)) {
175
+ /**
176
+ * check if we have a group choice instead of an assignment
177
+ */
178
+ if (this.curToken.Type === Tokens.SLASH && this.peekToken.Type === Tokens.SLASH) {
179
+ if (valuesOrProperties.length === 0) {
180
+ throw this.parserError('Unexpected group choice operator "//" at start of group');
181
+ }
182
+ if (!isChoice) {
183
+ const last = valuesOrProperties.pop();
184
+ valuesOrProperties.push([last]);
185
+ isChoice = true;
186
+ }
187
+ this.nextToken();
188
+ this.nextToken();
189
+ continue;
190
+ }
166
191
  const propertyType = [];
167
192
  const comments = [];
168
193
  let isUnwrapped = false;
@@ -197,13 +222,23 @@ export default class Parser {
197
222
  this.curToken.Literal === Tokens.LBRACK ||
198
223
  this.curToken.Literal === Tokens.LPAREN) {
199
224
  const innerGroup = this.parseAssignmentValue();
200
- valuesOrProperties.push({
225
+ const prop = {
201
226
  HasCut: false,
202
227
  Occurrence: occurrence,
203
228
  Name: '',
204
229
  Type: innerGroup,
205
230
  Comments: []
206
- });
231
+ };
232
+ if (isChoice) {
233
+ valuesOrProperties[valuesOrProperties.length - 1].push(prop);
234
+ }
235
+ else {
236
+ valuesOrProperties.push(prop);
237
+ }
238
+ if (this.curToken.Type === Tokens.COMMA) {
239
+ this.nextToken();
240
+ isChoice = false;
241
+ }
207
242
  continue;
208
243
  }
209
244
  /**
@@ -316,8 +351,8 @@ export default class Parser {
316
351
  * parse property value
317
352
  */
318
353
  const props = this.parseAssignmentValue();
319
- const operator = this.isOperator() ? this.parseOperator() : undefined;
320
- if (!isChoice && this.curToken.Type === Tokens.SLASH) {
354
+ let operator = this.isOperator() ? this.parseOperator() : undefined;
355
+ if (!isChoice && this.curToken.Type === Tokens.SLASH && this.peekToken.Type !== Tokens.SLASH) {
321
356
  this.nextToken();
322
357
  const nextType = this.parsePropertyType();
323
358
  if (Array.isArray(props)) {
@@ -326,9 +361,14 @@ export default class Parser {
326
361
  * of one, e.g. `(float .ge 1.0) / null`
327
362
  */
328
363
  props.push(nextType);
329
- this.nextToken();
364
+ if (!this.isOperator()) {
365
+ this.nextToken();
366
+ }
330
367
  }
331
368
  }
369
+ if (this.isOperator()) {
370
+ operator = this.parseOperator();
371
+ }
332
372
  if (Array.isArray(props)) {
333
373
  /**
334
374
  * property has multiple types (e.g. `float / tstr / int`)
@@ -480,10 +520,6 @@ export default class Parser {
480
520
  openSegment() {
481
521
  if (this.curToken.Type === Tokens.LBRACE) {
482
522
  this.nextToken();
483
- if (this.peekToken.Type === Tokens.LPAREN) {
484
- this.nextToken();
485
- return [Tokens.RPAREN, Tokens.RBRACE];
486
- }
487
523
  return [Tokens.RBRACE];
488
524
  }
489
525
  else if (this.curToken.Type === Tokens.LPAREN) {
@@ -544,6 +580,13 @@ export default class Parser {
544
580
  Unwrapped: isUnwrapped
545
581
  };
546
582
  }
583
+ else if (this.curToken.Literal === Tokens.LBRACE) {
584
+ const val = this.parseAssignmentValue();
585
+ if (Array.isArray(val)) {
586
+ throw new Error('Unexpected array in property type parsing');
587
+ }
588
+ type = val;
589
+ }
547
590
  else if (this.curToken.Type === Tokens.IDENT) {
548
591
  type = {
549
592
  Type: 'group',
@@ -611,6 +654,9 @@ export default class Parser {
611
654
  this.nextToken();
612
655
  }
613
656
  this.nextToken();
657
+ if (!type || (typeof type === 'object' && !('Value' in type))) {
658
+ throw new Error('Invalid type for range definition');
659
+ }
614
660
  const Min = typeof type === 'string' || typeof type.Value === 'number'
615
661
  ? type
616
662
  : type.Value;
@@ -641,7 +687,7 @@ export default class Parser {
641
687
  * as a grouped range.
642
688
  */
643
689
  this.nextToken();
644
- if (this.peekToken.Type === Tokens.DOT) {
690
+ if (this.isOperator()) {
645
691
  isGroupedRange = true;
646
692
  }
647
693
  }
@@ -649,6 +695,10 @@ export default class Parser {
649
695
  this.nextToken(); // eat ")"
650
696
  }
651
697
  }
698
+ if (!type) {
699
+ const { line, position: column } = this.l.getLocation();
700
+ throw new Error(`Unexpected type: ${this.curToken.Type} at line ${line} column ${column}`);
701
+ }
652
702
  return type;
653
703
  }
654
704
  parseOperator() {
@@ -0,0 +1,196 @@
1
+ # CDDL Core Concepts: Choices and Maps
2
+
3
+ This guide explains three fundamental concepts in CDDL that are often confused: **Type Choices**, **Group Choices**, and **Maps**.
4
+
5
+ ## 1. Type Choice (`/`)
6
+
7
+ A **Type Choice** allows a value to be one of several distinct types. It is represented by the single forward slash `/`.
8
+
9
+ Think of it as an "OR" operator for *values*.
10
+
11
+ ### Syntax
12
+ ```cddl
13
+ type1 / type2 / type3
14
+ ```
15
+
16
+ ### Examples
17
+
18
+ **Simple Value Choice:**
19
+ ```cddl
20
+ ; MyValue can be an integer OR a text string
21
+ MyValue = int / tstr
22
+ ```
23
+
24
+ **Inside an Array:**
25
+ ```cddl
26
+ ; An array where the first element is either an int OR a float
27
+ MyArray = [ int / float, tstr ]
28
+ ```
29
+
30
+ **Inside a Map Property:**
31
+ ```cddl
32
+ ; The "score" property can be an integer OR null
33
+ MyMap = {
34
+ "score" => int / null
35
+ }
36
+ ```
37
+
38
+ ---
39
+
40
+ ## 2. Group Choice (`//`)
41
+
42
+ A **Group Choice** allows a structure (like a map or array) to match one of several *groups* of properties. It is represented by the double forward slash `//`.
43
+
44
+ Think of it as an "OR" operator for *sets of fields/entries*.
45
+
46
+ ### Syntax
47
+ ```cddl
48
+ (group1) // (group2) // (group3)
49
+ ```
50
+
51
+ ### Examples
52
+
53
+ **Choice between sets of fields:**
54
+ ```cddl
55
+ ; A defined group that is either a "street address" OR a "po box"
56
+ Address = (
57
+ street: tstr,
58
+ city: tstr
59
+ ) // (
60
+ po-box: int,
61
+ city: tstr
62
+ )
63
+ ```
64
+
65
+ **Usage in Maps:**
66
+ When used inside a map, it means the map must contain *either* the keys from the first group *OR* the keys from the second group.
67
+
68
+ ```cddl
69
+ ; A user profile must have either a "username" OR an "email" (but not necessarily both, depending on other rules)
70
+ UserProfile = {
71
+ id: int,
72
+ (
73
+ username: tstr
74
+ ) // (
75
+ email: tstr
76
+ )
77
+ }
78
+ ```
79
+
80
+ **Key Difference from Type Choice:**
81
+ - `key: int / tstr` -> The key exists, and its value is either int or string.
82
+ - `(key1: int) // (key2: int)` -> Either the key `key1` exists OR the key `key2` exists.
83
+
84
+ ---
85
+
86
+ ## 3. Maps
87
+
88
+ In CDDL, a **Map** is defined using curly braces `{}`. It corresponds to a JSON Object or a dictionary/hash map.
89
+
90
+ Inside a Map, you define **Groups** of properties/pairs using `key => value` or `key: value` syntax.
91
+
92
+ ### Syntax
93
+ ```cddl
94
+ MyMap = {
95
+ key1 => value1,
96
+ key2: value2,
97
+ optional-key3?: value3
98
+ }
99
+ ```
100
+
101
+ ### Deep Dive: Maps vs. Groups
102
+
103
+ - A **Group** is just a sequence of fields (key-value pairs) inside a structure.
104
+ - A **Map** `{ ... }` acts as a container for a Group.
105
+
106
+ **Defining a reusable Group:**
107
+ ```cddl
108
+ ; "PersonFields" is just a list of fields, NOT a map yet.
109
+ PersonFields = (
110
+ name: tstr,
111
+ age: int
112
+ )
113
+ ```
114
+
115
+ **Using the Group in a Map:**
116
+ ```cddl
117
+ ; "Student" is a Map that contains the PersonFields
118
+ Student = {
119
+ PersonFields,
120
+ school: tstr
121
+ }
122
+
123
+ ; "Teacher" is also a Map that contains the PersonFields
124
+ Teacher = {
125
+ PersonFields,
126
+ subject: tstr
127
+ }
128
+ ```
129
+
130
+ ### Mixing Choices in Maps
131
+
132
+ You can combine Maps and Choices to create complex validation logic:
133
+
134
+ ```cddl
135
+ Response = {
136
+ "status" => "ok",
137
+ "data" => tstr
138
+ } / {
139
+ "status" => "error",
140
+ "code" => int,
141
+ "message" => tstr
142
+ }
143
+ ```
144
+ *This definition means a Response object must look like EITHER the first block (status="ok", data=...) OR the second block (status="error", code=...).*
145
+
146
+ ---
147
+
148
+ ## 4. Arrays
149
+
150
+ Arrays in CDDL are defined using square brackets `[]`. They correspond to JSON Arrays.
151
+
152
+ ### Syntax
153
+ ```cddl
154
+ MyArray = [ type1, type2, ... ]
155
+ ```
156
+
157
+ ### Examples
158
+
159
+ **Simple Array:**
160
+ ```cddl
161
+ ; An array of an integer, a string, and a float
162
+ Point3D = [ x: float, y: float, z: float ]
163
+ ```
164
+
165
+ **Array with Group Choice:**
166
+ You can use group choices inside arrays to allow different structures.
167
+
168
+ ```cddl
169
+ ; An item is either an integer OR a string
170
+ Item = [ (int // tstr) ]
171
+ ```
172
+
173
+ ---
174
+
175
+ ## 5. Literals
176
+
177
+ CDDL supports various literal values which are useful for exact matching.
178
+
179
+ ### Examples
180
+
181
+ ```cddl
182
+ LiteralTest = {
183
+ ; Exact string match
184
+ type: "widget",
185
+
186
+ ; Exact integer match
187
+ version: 1,
188
+
189
+ ; Big Integers
190
+ big_id: 9007199254740995,
191
+
192
+ ; Null and Booleans
193
+ is_active: true,
194
+ deleted_at: null
195
+ }
196
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cddl",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Concise data definition language (RFC 8610) implementation and JSON validator in Node.js",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "license": "MIT",
@@ -30,7 +30,8 @@
30
30
  "release:patch": "npm run release -- patch",
31
31
  "release:minor": "npm run release -- minor",
32
32
  "release:major": "npm run release -- major",
33
- "test": "vitest",
33
+ "test": "vitest run",
34
+ "checks:all": "npm run compile && npm run test",
34
35
  "watch": "tsc --watch"
35
36
  },
36
37
  "devDependencies": {
@@ -38,7 +39,7 @@
38
39
  "@types/node": "^22.15.3",
39
40
  "@vitest/coverage-v8": "^3.1.2",
40
41
  "npm-run-all": "^4.1.5",
41
- "release-it": "^19.0.1",
42
+ "release-it": "^19.2.4",
42
43
  "typescript": "^5.8.3",
43
44
  "vitest": "^3.1.2"
44
45
  },