cddl 0.14.10 → 0.16.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/build/index.d.ts CHANGED
@@ -7,4 +7,5 @@ declare const _default: {
7
7
  export default _default;
8
8
  export { Lexer, Parser };
9
9
  export * from './ast.js';
10
+ export * from './utils.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAA;AAC9B,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,wBAAgB,KAAK,CAAE,QAAQ,EAAE,MAAM,mCAGtC;;;;AAED,wBAAwB;AACxB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AACxB,cAAc,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAA;AAC9B,OAAO,MAAM,MAAM,aAAa,CAAA;AAEhC,wBAAgB,KAAK,CAAE,QAAQ,EAAE,MAAM,mCAGtC;;;;AAED,wBAAwB;AACxB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA"}
package/build/index.js CHANGED
@@ -7,3 +7,4 @@ export function parse(filePath) {
7
7
  export default { parse };
8
8
  export { Lexer, Parser };
9
9
  export * from './ast.js';
10
+ export * from './utils.js';
@@ -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;IAC7B,cAAc,EAAE,KAAK,CAAa;gBAErB,QAAQ,EAAE,MAAM;IAS7B,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,gBAAgB;IA2CxB,OAAO,CAAC,oBAAoB;IAue5B,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"}
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;IA8CxB,OAAO,CAAC,oBAAoB;IAue5B,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;IA8E1B,OAAO,CAAC,gBAAgB;IA4DxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB,KAAK;IAaL,OAAO,CAAC,WAAW;CAKtB"}
package/build/parser.js CHANGED
@@ -43,7 +43,10 @@ export default class Parser {
43
43
  parseAssignments() {
44
44
  const comments = [];
45
45
  while (this.curToken.Type === Tokens.COMMENT) {
46
- comments.push(this.parseComment());
46
+ const comment = this.parseComment();
47
+ if (comment) {
48
+ comments.push(comment);
49
+ }
47
50
  }
48
51
  /**
49
52
  * expect group identifier, e.g.
@@ -588,7 +591,7 @@ export default class Parser {
588
591
  Unwrapped: isUnwrapped
589
592
  };
590
593
  }
591
- else if (this.curToken.Literal === Tokens.LBRACE) {
594
+ else if (this.curToken.Literal === Tokens.LBRACE || this.curToken.Literal === Tokens.LBRACK) {
592
595
  const val = this.parseAssignmentValue();
593
596
  if (Array.isArray(val)) {
594
597
  throw new Error('Unexpected array in property type parsing');
@@ -739,6 +742,21 @@ export default class Parser {
739
742
  this.nextToken(); // eat Property if not already consumed (e.g. by Group parsing)
740
743
  }
741
744
  propertyTypes.push(prop);
745
+ /**
746
+ * ignore comments between type choice members, e.g.
747
+ * ```
748
+ * Foo = int ; comment
749
+ * / text
750
+ * ```
751
+ * or
752
+ * ```
753
+ * Foo = int / ; comment
754
+ * text
755
+ * ```
756
+ */
757
+ while (this.curToken.Type === Tokens.COMMENT && this.peekToken.Type === Tokens.SLASH) {
758
+ this.parseComment();
759
+ }
742
760
  /**
743
761
  * ensure we don't go into the next choice, e.g.:
744
762
  * ```
@@ -754,6 +772,9 @@ export default class Parser {
754
772
  */
755
773
  while (this.curToken.Type === Tokens.SLASH) {
756
774
  this.nextToken(); // eat `/`
775
+ while ([Tokens.COMMENT].includes(this.curToken.Type)) {
776
+ this.parseComment();
777
+ }
757
778
  propertyTypes.push(this.parsePropertyType());
758
779
  if (!this.isOperator() && this.curToken.Type !== Tokens.SLASH) {
759
780
  /**
@@ -762,6 +783,9 @@ export default class Parser {
762
783
  */
763
784
  this.nextToken();
764
785
  }
786
+ while ([Tokens.COMMENT].includes(this.curToken.Type) && this.peekToken.Type === Tokens.SLASH) {
787
+ this.parseComment();
788
+ }
765
789
  /**
766
790
  * ensure we don't go into the next choice, e.g.:
767
791
  * ```
package/build/utils.d.ts CHANGED
@@ -1,7 +1,26 @@
1
+ import type { Assignment, Array as CDDLArray, Group, NativeTypeWithOperator, Property, PropertyReference, Variable } from './ast.js';
1
2
  import { Token } from './tokens.js';
2
3
  export declare function isLetter(ch: string): boolean;
3
4
  export declare function isAlphabeticCharacter(ch: string): boolean;
4
5
  export declare function isDigit(ch: string): boolean;
5
6
  export declare function hasSpecialNumberCharacter(ch: number): boolean;
6
7
  export declare function parseNumberValue(token: Token): string | number;
8
+ export declare function pascalCase(name: string): string;
9
+ export declare function isVariable(assignment: Assignment): assignment is Variable;
10
+ export declare function isGroup(t: any): t is Group;
11
+ export declare function isCDDLArray(t: any): t is CDDLArray;
12
+ export declare function isProperty(t: any): t is Property;
13
+ export declare function isUnNamedProperty(t: any): t is Property & {
14
+ Name: '';
15
+ };
16
+ export declare function isNamedGroupReference(t: any): t is PropertyReference & {
17
+ Value: string;
18
+ };
19
+ export declare function isPropertyReference(t: any): t is PropertyReference;
20
+ export declare function isNativeTypeWithOperator(t: any): t is NativeTypeWithOperator;
21
+ export declare function isRange(t: any): boolean;
22
+ export declare function isLiteralWithValue(t: any): t is {
23
+ Type: 'literal';
24
+ Value: unknown;
25
+ };
7
26
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,EAAE,MAAM,aAAa,CAAA;AAE3C,wBAAgB,QAAQ,CAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,wBAAgB,qBAAqB,CAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,OAAO,CAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,yBAAyB,CAAE,EAAE,EAAE,MAAM,WAOpD;AAED,wBAAgB,gBAAgB,CAAE,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAa/D"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,UAAU,EACV,KAAK,IAAI,SAAS,EAClB,KAAK,EACL,sBAAsB,EACtB,QAAQ,EACR,iBAAiB,EACjB,QAAQ,EACX,MAAM,UAAU,CAAA;AAEjB,OAAO,EAAU,KAAK,EAAE,MAAM,aAAa,CAAA;AAE3C,wBAAgB,QAAQ,CAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE7C;AAED,wBAAgB,qBAAqB,CAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,OAAO,CAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED,wBAAgB,yBAAyB,CAAE,EAAE,EAAE,MAAM,WAOpD;AAED,wBAAgB,gBAAgB,CAAE,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAa/D;AAED,wBAAgB,UAAU,CAAE,IAAI,EAAE,MAAM,UAEvC;AAED,wBAAgB,UAAU,CAAE,UAAU,EAAE,UAAU,GAAG,UAAU,IAAI,QAAQ,CAE1E;AAED,wBAAgB,OAAO,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,KAAK,CAE3C;AAED,wBAAgB,WAAW,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,SAAS,CAEnD;AAED,wBAAgB,UAAU,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,CAEjD;AAED,wBAAgB,iBAAiB,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,GAAG;IAAE,IAAI,EAAE,EAAE,CAAA;CAAE,CAEvE;AAED,wBAAgB,qBAAqB,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,iBAAiB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAEzF;AAED,wBAAgB,mBAAmB,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,iBAAiB,CAEnE;AAED,wBAAgB,wBAAwB,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,sBAAsB,CAE7E;AAED,wBAAgB,OAAO,CAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAExC;AAED,wBAAgB,kBAAkB,CAAE,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI;IAC9C,IAAI,EAAE,SAAS,CAAA;IACf,KAAK,EAAE,OAAO,CAAA;CACjB,CAEA"}
package/build/utils.js CHANGED
@@ -1,3 +1,4 @@
1
+ import camelcase from 'camelcase';
1
2
  import { Tokens } from './tokens.js';
2
3
  export function isLetter(ch) {
3
4
  return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z';
@@ -24,3 +25,36 @@ export function parseNumberValue(token) {
24
25
  }
25
26
  return parseInt(token.Literal, 10);
26
27
  }
28
+ export function pascalCase(name) {
29
+ return camelcase(name, { pascalCase: true });
30
+ }
31
+ export function isVariable(assignment) {
32
+ return assignment.Type === 'variable';
33
+ }
34
+ export function isGroup(t) {
35
+ return t && t.Type === 'group';
36
+ }
37
+ export function isCDDLArray(t) {
38
+ return t && t.Type === 'array';
39
+ }
40
+ export function isProperty(t) {
41
+ return t && typeof t.Name === 'string' && typeof t.HasCut === 'boolean';
42
+ }
43
+ export function isUnNamedProperty(t) {
44
+ return isProperty(t) && t.Name === '';
45
+ }
46
+ export function isNamedGroupReference(t) {
47
+ return isGroup(t) && isPropertyReference(t) && typeof t.Value === 'string';
48
+ }
49
+ export function isPropertyReference(t) {
50
+ return t && typeof t === 'object' && 'Value' in t;
51
+ }
52
+ export function isNativeTypeWithOperator(t) {
53
+ return t && typeof t.Type === 'object' && 'Operator' in t;
54
+ }
55
+ export function isRange(t) {
56
+ return t && typeof t.Type === 'object' && t.Type.Type === 'range';
57
+ }
58
+ export function isLiteralWithValue(t) {
59
+ return t && t.Type === 'literal' && 'Value' in t;
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cddl",
3
- "version": "0.14.10",
3
+ "version": "0.16.0",
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",
@@ -26,6 +26,7 @@
26
26
  "@types/yargs": "^17.0.35"
27
27
  },
28
28
  "dependencies": {
29
+ "camelcase": "^9.0.0",
29
30
  "yargs": "^18.0.0"
30
31
  },
31
32
  "scripts": {
package/src/index.ts CHANGED
@@ -9,3 +9,4 @@ export function parse (filePath: string) {
9
9
  export default { parse }
10
10
  export { Lexer, Parser }
11
11
  export * from './ast.js'
12
+ export * from './utils.js'
package/src/parser.ts CHANGED
@@ -56,7 +56,10 @@ export default class Parser {
56
56
  private parseAssignments (): Assignment {
57
57
  const comments: Comment[] = []
58
58
  while (this.curToken.Type === Tokens.COMMENT) {
59
- comments.push(this.parseComment()!)
59
+ const comment = this.parseComment()
60
+ if (comment) {
61
+ comments.push(comment)
62
+ }
60
63
  }
61
64
 
62
65
  /**
@@ -667,12 +670,12 @@ export default class Parser {
667
670
  Value: this.curToken.Literal === 'true',
668
671
  Unwrapped: isUnwrapped
669
672
  }
670
- } else if (this.curToken.Literal === Tokens.LBRACE) {
671
- const val = this.parseAssignmentValue();
672
- if (Array.isArray(val)) {
673
- throw new Error('Unexpected array in property type parsing');
674
- }
675
- type = val
673
+ } else if (this.curToken.Literal === Tokens.LBRACE || this.curToken.Literal === Tokens.LBRACK) {
674
+ const val = this.parseAssignmentValue()
675
+ if (Array.isArray(val)) {
676
+ throw new Error('Unexpected array in property type parsing')
677
+ }
678
+ type = val
676
679
  } else if (this.curToken.Type === Tokens.IDENT) {
677
680
  type = {
678
681
  Type: 'group' as PropertyReferenceType,
@@ -829,6 +832,22 @@ export default class Parser {
829
832
 
830
833
  propertyTypes.push(prop)
831
834
 
835
+ /**
836
+ * ignore comments between type choice members, e.g.
837
+ * ```
838
+ * Foo = int ; comment
839
+ * / text
840
+ * ```
841
+ * or
842
+ * ```
843
+ * Foo = int / ; comment
844
+ * text
845
+ * ```
846
+ */
847
+ while (this.curToken.Type === Tokens.COMMENT && this.peekToken.Type === Tokens.SLASH) {
848
+ this.parseComment()
849
+ }
850
+
832
851
  /**
833
852
  * ensure we don't go into the next choice, e.g.:
834
853
  * ```
@@ -845,6 +864,9 @@ export default class Parser {
845
864
  */
846
865
  while (this.curToken.Type === Tokens.SLASH) {
847
866
  this.nextToken() // eat `/`
867
+ while ([Tokens.COMMENT].includes(this.curToken.Type)) {
868
+ this.parseComment()
869
+ }
848
870
  propertyTypes.push(this.parsePropertyType())
849
871
  if (!this.isOperator() && this.curToken.Type !== Tokens.SLASH) {
850
872
  /**
@@ -854,6 +876,10 @@ export default class Parser {
854
876
  this.nextToken()
855
877
  }
856
878
 
879
+ while ([Tokens.COMMENT].includes(this.curToken.Type) && this.peekToken.Type === Tokens.SLASH) {
880
+ this.parseComment()
881
+ }
882
+
857
883
  /**
858
884
  * ensure we don't go into the next choice, e.g.:
859
885
  * ```
package/src/utils.ts CHANGED
@@ -1,3 +1,15 @@
1
+ import camelcase from 'camelcase'
2
+
3
+ import type {
4
+ Assignment,
5
+ Array as CDDLArray,
6
+ Group,
7
+ NativeTypeWithOperator,
8
+ Property,
9
+ PropertyReference,
10
+ Variable
11
+ } from './ast.js'
12
+
1
13
  import { Tokens, Token } from './tokens.js'
2
14
 
3
15
  export function isLetter (ch: string): boolean {
@@ -35,3 +47,50 @@ export function parseNumberValue (token: Token): string | number {
35
47
 
36
48
  return parseInt(token.Literal, 10)
37
49
  }
50
+
51
+ export function pascalCase (name: string) {
52
+ return camelcase(name, { pascalCase: true })
53
+ }
54
+
55
+ export function isVariable (assignment: Assignment): assignment is Variable {
56
+ return assignment.Type === 'variable'
57
+ }
58
+
59
+ export function isGroup (t: any): t is Group {
60
+ return t && t.Type === 'group'
61
+ }
62
+
63
+ export function isCDDLArray (t: any): t is CDDLArray {
64
+ return t && t.Type === 'array'
65
+ }
66
+
67
+ export function isProperty (t: any): t is Property {
68
+ return t && typeof t.Name === 'string' && typeof t.HasCut === 'boolean'
69
+ }
70
+
71
+ export function isUnNamedProperty (t: any): t is Property & { Name: '' } {
72
+ return isProperty(t) && t.Name === ''
73
+ }
74
+
75
+ export function isNamedGroupReference (t: any): t is PropertyReference & { Value: string } {
76
+ return isGroup(t) && isPropertyReference(t) && typeof t.Value === 'string'
77
+ }
78
+
79
+ export function isPropertyReference (t: any): t is PropertyReference {
80
+ return t && typeof t === 'object' && 'Value' in t
81
+ }
82
+
83
+ export function isNativeTypeWithOperator (t: any): t is NativeTypeWithOperator {
84
+ return t && typeof t.Type === 'object' && 'Operator' in t
85
+ }
86
+
87
+ export function isRange (t: any): boolean {
88
+ return t && typeof t.Type === 'object' && (t.Type as any).Type === 'range'
89
+ }
90
+
91
+ export function isLiteralWithValue (t: any): t is {
92
+ Type: 'literal'
93
+ Value: unknown
94
+ } {
95
+ return t && t.Type === 'literal' && 'Value' in t
96
+ }
@@ -262,6 +262,131 @@ exports[`Group Choice Parsing > Type Choice (/) > should correctly handle slash
262
262
  ]
263
263
  `;
264
264
 
265
+ exports[`Group Choice Parsing > Type Choice (/) > should parse inline array alternatives inside map properties 1`] = `
266
+ [
267
+ {
268
+ "Comments": [],
269
+ "IsChoiceAddition": false,
270
+ "Name": "StorePutParams",
271
+ "Properties": [
272
+ {
273
+ "Comments": [],
274
+ "HasCut": true,
275
+ "Name": "storeNamespace",
276
+ "Occurrence": {
277
+ "m": 1,
278
+ "n": 1,
279
+ },
280
+ "Type": [
281
+ {
282
+ "Comments": [],
283
+ "Name": "",
284
+ "Type": "array",
285
+ "Values": [
286
+ {
287
+ "Comments": [],
288
+ "HasCut": false,
289
+ "Name": "",
290
+ "Occurrence": {
291
+ "m": Infinity,
292
+ "n": 0,
293
+ },
294
+ "Type": "text",
295
+ },
296
+ ],
297
+ },
298
+ ],
299
+ },
300
+ {
301
+ "Comments": [],
302
+ "HasCut": true,
303
+ "Name": "key",
304
+ "Occurrence": {
305
+ "m": 1,
306
+ "n": 1,
307
+ },
308
+ "Type": [
309
+ "text",
310
+ ],
311
+ },
312
+ {
313
+ "Comments": [],
314
+ "HasCut": true,
315
+ "Name": "value",
316
+ "Occurrence": {
317
+ "m": 1,
318
+ "n": 1,
319
+ },
320
+ "Type": [
321
+ {
322
+ "Comments": [],
323
+ "IsChoiceAddition": false,
324
+ "Name": "",
325
+ "Properties": [
326
+ {
327
+ "Comments": [],
328
+ "HasCut": false,
329
+ "Name": "text",
330
+ "Occurrence": {
331
+ "m": Infinity,
332
+ "n": 0,
333
+ },
334
+ "Type": [
335
+ "any",
336
+ ],
337
+ },
338
+ ],
339
+ "Type": "group",
340
+ },
341
+ ],
342
+ },
343
+ {
344
+ "Comments": [],
345
+ "HasCut": true,
346
+ "Name": "index",
347
+ "Occurrence": {
348
+ "m": Infinity,
349
+ "n": 0,
350
+ },
351
+ "Type": [
352
+ "bool",
353
+ {
354
+ "Comments": [],
355
+ "Name": "",
356
+ "Type": "array",
357
+ "Values": [
358
+ {
359
+ "Comments": [],
360
+ "HasCut": false,
361
+ "Name": "",
362
+ "Occurrence": {
363
+ "m": Infinity,
364
+ "n": 0,
365
+ },
366
+ "Type": "text",
367
+ },
368
+ ],
369
+ },
370
+ ],
371
+ },
372
+ {
373
+ "Comments": [],
374
+ "HasCut": true,
375
+ "Name": "ttl",
376
+ "Occurrence": {
377
+ "m": Infinity,
378
+ "n": 0,
379
+ },
380
+ "Type": [
381
+ "uint",
382
+ ],
383
+ },
384
+ ],
385
+ "Type": "group",
386
+ },
387
+ ]
388
+ `;
389
+
265
390
  exports[`Group Choice Parsing > Type Choice (/) > should parse type choice inside group 1`] = `
266
391
  [
267
392
  {
@@ -70,6 +70,21 @@ describe('Group Choice Parsing', () => {
70
70
  expect(ast).toMatchSnapshot()
71
71
  })
72
72
 
73
+ it('should treat comments around type choice separators the same', () => {
74
+ const slashAfterComment = `
75
+ FlowStrategy = "drop-oldest" ; Discard oldest when buffer is full
76
+ / "pause-producer" ; Apply backpressure to slow production
77
+ / "sample" ; Deliver every Nth event under pressure
78
+ `
79
+ const slashBeforeComment = `
80
+ FlowStrategy = "drop-oldest" / ; Discard oldest when buffer is full
81
+ "pause-producer" / ; Apply backpressure to slow production
82
+ "sample" ; Deliver every Nth event under pressure
83
+ `
84
+
85
+ expect(parse(slashAfterComment)).toEqual(parse(slashBeforeComment))
86
+ })
87
+
73
88
  // This tests the change: closingTokens.includes(Tokens.RPAREN) && this.peekToken.Type === Tokens.SLASH
74
89
  it('should correctly handle slash in mixed context', () => {
75
90
  const cddl = `
@@ -78,6 +93,20 @@ describe('Group Choice Parsing', () => {
78
93
  const ast = parse(cddl)
79
94
  expect(ast).toMatchSnapshot()
80
95
  })
96
+
97
+ it('should parse inline array alternatives inside map properties', () => {
98
+ const cddl = `
99
+ StorePutParams = {
100
+ storeNamespace: [* text],
101
+ key: text,
102
+ value: {* text => any},
103
+ ? index: bool / [* text],
104
+ ? ttl: uint,
105
+ }
106
+ `
107
+ const ast = parse(cddl)
108
+ expect(ast).toMatchSnapshot()
109
+ })
81
110
  })
82
111
 
83
112
  describe('Blocks with Braces', () => {
@@ -41,4 +41,23 @@ describe('parser', () => {
41
41
  expect(() => p.parse()).toThrow('group identifier expected')
42
42
  vi.restoreAllMocks()
43
43
  })
44
+
45
+ it('skips blank comment lines in assignment comments', () => {
46
+ vi.spyOn(fs, 'readFileSync').mockReturnValue('; heading\n;\nfoo = int\n')
47
+ const p = new Parser('foo.cddl')
48
+
49
+ expect(p.parse()).toEqual([{
50
+ Type: 'variable',
51
+ Name: 'foo',
52
+ IsChoiceAddition: false,
53
+ PropertyType: ['int'],
54
+ Comments: [{
55
+ Type: 'comment',
56
+ Content: 'heading',
57
+ Leading: false
58
+ }]
59
+ }])
60
+
61
+ vi.restoreAllMocks()
62
+ })
44
63
  })
@@ -0,0 +1,211 @@
1
+ import { describe, it, expect } from 'vitest'
2
+
3
+ import type {
4
+ Array as CDDLArray,
5
+ Assignment,
6
+ Comment,
7
+ Group,
8
+ NativeTypeWithOperator,
9
+ Property,
10
+ PropertyReference,
11
+ Variable
12
+ } from '../src/ast.js'
13
+ import { Tokens, type Token } from '../src/tokens.js'
14
+ import {
15
+ hasSpecialNumberCharacter,
16
+ isAlphabeticCharacter,
17
+ isCDDLArray,
18
+ isDigit,
19
+ isGroup,
20
+ isLetter,
21
+ isLiteralWithValue,
22
+ isNamedGroupReference,
23
+ isNativeTypeWithOperator,
24
+ isProperty,
25
+ isPropertyReference,
26
+ isRange,
27
+ isUnNamedProperty,
28
+ isVariable,
29
+ parseNumberValue,
30
+ pascalCase
31
+ } from '../src/utils.js'
32
+
33
+ const comments: Comment[] = []
34
+
35
+ function createVariable (): Variable {
36
+ return {
37
+ Type: 'variable',
38
+ Name: 'my-variable',
39
+ IsChoiceAddition: false,
40
+ PropertyType: 'tstr',
41
+ Comments: comments
42
+ }
43
+ }
44
+
45
+ function createGroup (): Group {
46
+ return {
47
+ Type: 'group',
48
+ Name: 'my-group',
49
+ IsChoiceAddition: false,
50
+ Properties: [],
51
+ Comments: comments
52
+ }
53
+ }
54
+
55
+ function createArray (): CDDLArray {
56
+ return {
57
+ Type: 'array',
58
+ Name: 'my-array',
59
+ Values: [],
60
+ Comments: comments
61
+ }
62
+ }
63
+
64
+ function createProperty (overrides: Partial<Property> = {}): Property {
65
+ return {
66
+ HasCut: false,
67
+ Occurrence: { n: 1, m: 1 },
68
+ Name: 'foo',
69
+ Type: 'tstr',
70
+ Comments: comments,
71
+ ...overrides
72
+ }
73
+ }
74
+
75
+ describe('utils', () => {
76
+ describe('character helpers', () => {
77
+ it('should detect letters and alphabetic characters', () => {
78
+ expect(isLetter('a')).toBe(true)
79
+ expect(isLetter('Z')).toBe(true)
80
+ expect(isLetter('1')).toBe(false)
81
+
82
+ expect(isAlphabeticCharacter('a')).toBe(true)
83
+ expect(isAlphabeticCharacter(Tokens.ATSIGN)).toBe(true)
84
+ expect(isAlphabeticCharacter(Tokens.UNDERSCORE)).toBe(true)
85
+ expect(isAlphabeticCharacter(Tokens.DOLLAR)).toBe(true)
86
+ expect(isAlphabeticCharacter('-')).toBe(false)
87
+ })
88
+
89
+ it('should detect digits and special number characters', () => {
90
+ expect(isDigit('1')).toBe(true)
91
+ expect(isDigit('0')).toBe(true)
92
+ expect(isDigit('a')).toBe(false)
93
+ expect(isDigit(Tokens.NL)).toBe(false)
94
+ expect(isDigit(Tokens.SPACE)).toBe(false)
95
+
96
+ expect(hasSpecialNumberCharacter(Tokens.MINUS.charCodeAt(0))).toBe(true)
97
+ expect(hasSpecialNumberCharacter(Tokens.DOT.charCodeAt(0))).toBe(true)
98
+ expect(hasSpecialNumberCharacter('x'.charCodeAt(0))).toBe(true)
99
+ expect(hasSpecialNumberCharacter('b'.charCodeAt(0))).toBe(true)
100
+ expect(hasSpecialNumberCharacter('1'.charCodeAt(0))).toBe(false)
101
+ })
102
+ })
103
+
104
+ describe('number parsing helpers', () => {
105
+ it('should parse floats, integers and prefixed numbers', () => {
106
+ const floatToken: Token = { Type: Tokens.FLOAT, Literal: '12.5' }
107
+ const intToken: Token = { Type: Tokens.INT, Literal: '42' }
108
+ const hexToken: Token = { Type: Tokens.INT, Literal: '0x10' }
109
+ const binaryToken: Token = { Type: Tokens.INT, Literal: '0b10' }
110
+
111
+ expect(parseNumberValue(floatToken)).toBe(12.5)
112
+ expect(parseNumberValue(intToken)).toBe(42)
113
+ expect(parseNumberValue(hexToken)).toBe('0x10')
114
+ expect(parseNumberValue(binaryToken)).toBe('0b10')
115
+ })
116
+ })
117
+
118
+ describe('name helpers', () => {
119
+ it('should convert names to pascal case', () => {
120
+ expect(pascalCase('my-example_name')).toBe('MyExampleName')
121
+ })
122
+ })
123
+
124
+ describe('assignment guards', () => {
125
+ it('should detect variables, groups and arrays', () => {
126
+ const variable: Assignment = createVariable()
127
+ const group: Assignment = createGroup()
128
+ const array: Assignment = createArray()
129
+
130
+ expect(isVariable(variable)).toBe(true)
131
+ expect(isVariable(group)).toBe(false)
132
+
133
+ expect(isGroup(group)).toBe(true)
134
+ expect(isGroup(variable)).toBe(false)
135
+
136
+ expect(isCDDLArray(array)).toBe(true)
137
+ expect(isCDDLArray(group)).toBe(false)
138
+ })
139
+ })
140
+
141
+ describe('property guards', () => {
142
+ it('should detect named and unnamed properties', () => {
143
+ const property = createProperty()
144
+ const unnamedProperty = createProperty({ Name: '' })
145
+
146
+ expect(isProperty(property)).toBe(true)
147
+ expect(isProperty({ Name: 'foo' })).toBe(false)
148
+
149
+ expect(isUnNamedProperty(unnamedProperty)).toBe(true)
150
+ expect(isUnNamedProperty(property)).toBe(false)
151
+ })
152
+ })
153
+
154
+ describe('reference and operator guards', () => {
155
+ it('should detect property references and named group references', () => {
156
+ const groupReference: PropertyReference = {
157
+ Type: 'group',
158
+ Value: 'my-group',
159
+ Unwrapped: false
160
+ }
161
+ const numericGroupReference: PropertyReference = {
162
+ Type: 'group',
163
+ Value: 123,
164
+ Unwrapped: false
165
+ }
166
+
167
+ expect(isPropertyReference(groupReference)).toBe(true)
168
+ expect(isPropertyReference({ Type: 'group' })).toBe(false)
169
+
170
+ expect(isNamedGroupReference(groupReference)).toBe(true)
171
+ expect(isNamedGroupReference(numericGroupReference)).toBe(false)
172
+ })
173
+
174
+ it('should detect native types with operators and ranges', () => {
175
+ const nativeTypeWithOperator: NativeTypeWithOperator = {
176
+ Type: {
177
+ Type: 'group',
178
+ Value: 'my-group',
179
+ Unwrapped: false
180
+ },
181
+ Operator: {
182
+ Type: 'default',
183
+ Value: 'tstr'
184
+ }
185
+ }
186
+ const rangeReference: PropertyReference = {
187
+ Type: 'range',
188
+ Value: {
189
+ Min: 0,
190
+ Max: 10,
191
+ Inclusive: true
192
+ },
193
+ Unwrapped: false
194
+ }
195
+
196
+ expect(isNativeTypeWithOperator(nativeTypeWithOperator)).toBe(true)
197
+ expect(isNativeTypeWithOperator({ Type: 'tstr' })).toBe(false)
198
+
199
+ expect(isRange({ Type: rangeReference })).toBe(true)
200
+ expect(isRange({ Type: 'range' })).toBe(false)
201
+ })
202
+ })
203
+
204
+ describe('literal guards', () => {
205
+ it('should detect literals with values', () => {
206
+ expect(isLiteralWithValue({ Type: 'literal', Value: 'foo' })).toBe(true)
207
+ expect(isLiteralWithValue({ Type: 'literal' })).toBe(false)
208
+ expect(isLiteralWithValue({ Type: 'group', Value: 'foo' })).toBe(false)
209
+ })
210
+ })
211
+ })