cddl 0.5.0 → 0.7.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/.editorconfig ADDED
@@ -0,0 +1,18 @@
1
+ # EditorConfig helps developers define and maintain consistent
2
+ # coding styles between different editors and IDEs
3
+ # editorconfig.org
4
+
5
+ root = true
6
+
7
+ [*]
8
+
9
+ indent_style = space
10
+ indent_size = 4
11
+
12
+ end_of_line = lf
13
+ charset = utf-8
14
+ trim_trailing_whitespace = true
15
+ insert_final_newline = true
16
+
17
+ [{.travis.yml,**/*.json,.github/**/*.yml}]
18
+ indent_size = 2
package/README.md CHANGED
@@ -20,7 +20,25 @@ $ npm install cddl
20
20
 
21
21
  ## Using this package
22
22
 
23
- Currently, you can use this package to parse a CDDL file into an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST). For example, given the following CDDL file:
23
+ This package exposes a CLI as well as a programmatic interface for parsing and transforming CDDL.
24
+
25
+ ### CLI
26
+
27
+ The `cddl` CLI offers a `validate` command that helps identify invalid CDDL formats, e.g.:
28
+
29
+ ```sh
30
+ npx cddl validate ./path/to/interface.cddl
31
+ ✅ Valid CDDL file!
32
+ ```
33
+
34
+ ### Programmatic Interface
35
+
36
+ You can also use this package to parse a CDDL file into:
37
+
38
+ - an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST).
39
+ - or a [TypeScript](https://www.typescriptlang.org/) definition
40
+
41
+ For example, given the following CDDL file:
24
42
 
25
43
  ```cddl
26
44
  person = {
@@ -29,7 +47,7 @@ person = {
29
47
  }
30
48
  ```
31
49
 
32
- You can use this package to parse the file into an abstract syntax tree (AST):
50
+ By default the package parses the content into an AST:
33
51
 
34
52
  ```js
35
53
  import { parse } from 'cddl'
@@ -49,6 +67,35 @@ console.log(ast)
49
67
  */
50
68
  ```
51
69
 
70
+ You can apply a target specifier to transform the AST into a different language or format (currently supported: `ts` for TypeScript). Note that this is highly experimental and work in progress.
71
+
72
+ ```js
73
+ import { parse } from 'cddl'
74
+
75
+ /**
76
+ * spec.cddl:
77
+ *
78
+ * session.CapabilityRequest = {
79
+ * ?acceptInsecureCerts: bool,
80
+ * ?browserName: text,
81
+ * ?browserVersion: text,
82
+ * ?platformName: text,
83
+ * };
84
+ */
85
+ const ts = parse('./spec.cddl', { target: 'ts' })
86
+ console.log(ts)
87
+ /**
88
+ * outputs:
89
+ *
90
+ * interface SessionCapabilityRequest {
91
+ * acceptInsecureCerts?: boolean,
92
+ * browserName?: string,
93
+ * browserVersion?: string,
94
+ * platformName?: string,
95
+ * }
96
+ */
97
+ ```
98
+
52
99
  ---
53
100
 
54
101
  If you are interested in this project, please feel free to contribute ideas or code patches. Have a look at our [contributing](https://github.com/christian-bromann/cddl/blob/master/LICENSE) guidelines](https://github.com/christian-bromann/cddl/blob/master/LICENSE) to get started.
package/build/ast.d.ts CHANGED
@@ -13,6 +13,7 @@ export type Group = {
13
13
  Name: string;
14
14
  IsChoiceAddition: boolean;
15
15
  Properties: (Property | Property[])[];
16
+ Comments: Comment[];
16
17
  };
17
18
  /**
18
19
  * an array definition
@@ -26,6 +27,7 @@ export type Array = {
26
27
  Type: 'array';
27
28
  Name: string;
28
29
  Values: (Property | Property[])[];
30
+ Comments: Comment[];
29
31
  };
30
32
  /**
31
33
  * a tag definition
@@ -48,6 +50,8 @@ export type Variable = {
48
50
  Name: string;
49
51
  IsChoiceAddition: boolean;
50
52
  PropertyType: PropertyType | PropertyType[];
53
+ Operator?: Operator;
54
+ Comments: Comment[];
51
55
  };
52
56
  /**
53
57
  * a comment statement
@@ -58,6 +62,7 @@ export type Variable = {
58
62
  export type Comment = {
59
63
  Type: 'comment';
60
64
  Content: string;
65
+ Leading: boolean;
61
66
  };
62
67
  export type Occurrence = {
63
68
  n: number;
@@ -68,7 +73,8 @@ export type Property = {
68
73
  Occurrence: Occurrence;
69
74
  Name: PropertyName;
70
75
  Type: PropertyType | PropertyType[];
71
- Comment: string;
76
+ Comments: Comment[];
77
+ Operator?: Operator;
72
78
  };
73
79
  export declare enum Type {
74
80
  /**
@@ -91,7 +97,12 @@ export declare enum Type {
91
97
  BSTR = "bstr",
92
98
  BYTES = "bytes",
93
99
  TSTR = "tstr",
94
- TEXT = "text"
100
+ TEXT = "text",
101
+ /**
102
+ * null types
103
+ */
104
+ NIL = "nil",
105
+ NULL = "null"
95
106
  }
96
107
  /**
97
108
  * can be a number, e.g. "foo = 0..10"
@@ -115,13 +126,23 @@ export type Range = {
115
126
  Max: RangePropertyReference;
116
127
  Inclusive: boolean;
117
128
  };
118
- export type PropertyReferenceType = 'literal' | 'group' | 'group_array' | 'range' | 'tag';
129
+ export type OperatorType = 'default' | 'size' | 'regexp' | 'bits' | 'and' | 'within' | 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge';
130
+ export interface Operator {
131
+ Type: OperatorType;
132
+ Value: PropertyType;
133
+ }
134
+ export type PropertyReferenceType = 'literal' | 'group' | 'group_array' | 'array' | 'range' | 'tag';
119
135
  export type PropertyReference = {
120
136
  Type: PropertyReferenceType;
121
137
  Value: string | number | boolean | Group | Array | Range | Tag;
122
138
  Unwrapped: boolean;
139
+ Operator?: Operator;
123
140
  };
124
- export type Assignment = Group | Array | Variable | Comment;
125
- export type PropertyType = Assignment | Array | PropertyReference | string;
141
+ export interface NativeTypeWithOperator {
142
+ Type: Type | PropertyReference;
143
+ Operator?: Operator;
144
+ }
145
+ export type Assignment = Group | Array | Variable;
146
+ export type PropertyType = Assignment | Array | PropertyReference | string | NativeTypeWithOperator;
126
147
  export type PropertyName = string;
127
148
  //# sourceMappingURL=ast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,KAAK,GAAG;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,CAAC,QAAQ,GAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;CACvC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,KAAK,GAAG;IAChB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,CAAC,QAAQ,GAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;CACnC,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,GAAG,GAAG;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG;IACnB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;CAC/C,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACrB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACb,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,YAAY,GAAG,YAAY,EAAE,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;CACnB,CAAA;AAED,oBAAY,IAAI;IACZ;;OAEG;IAEH,IAAI,SAAS;IAEb;;OAEG;IAEH,GAAG,QAAQ;IAEX,IAAI,SAAS;IAEb,IAAI,SAAS;IAEb,KAAK,UAAU;IAEf,OAAO,YAAY;IAEnB,OAAO,YAAY;IAEnB,OAAO,YAAY;IAEnB;;OAEG;IAEH,IAAI,SAAS;IAEb,KAAK,UAAU;IAEf,IAAI,SAAS;IAEb,IAAI,SAAS;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,MAAM,CAAA;AAEpD,MAAM,MAAM,KAAK,GAAG;IAChB,GAAG,EAAE,sBAAsB,CAAC;IAC5B,GAAG,EAAE,sBAAsB,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,KAAK,CAAA;AACzF,MAAM,MAAM,iBAAiB,GAAG;IAC5B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,GAAG,CAAC;IAC/D,SAAS,EAAE,OAAO,CAAC;CACtB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,CAAC;AAC5D,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,KAAK,GAAG,iBAAiB,GAAG,MAAM,CAAA;AAC1E,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../src/ast.ts"],"names":[],"mappings":"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"}
package/build/ast.js CHANGED
@@ -33,4 +33,9 @@ export var Type;
33
33
  Type["TSTR"] = "tstr";
34
34
  // Text string (major type 3)
35
35
  Type["TEXT"] = "text";
36
+ /**
37
+ * null types
38
+ */
39
+ Type["NIL"] = "nil";
40
+ Type["NULL"] = "null";
36
41
  })(Type || (Type = {}));
package/build/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import Parser from './parser.js';
3
3
  import { ParseTargets } from './constants.js';
4
4
  import type { ParseOptions } from './types.js';
5
5
  declare const _default: {
6
- parse: (filePath: string, opts: ParseOptions) => string | import("./ast.js").Assignment[];
6
+ parse: (filePath: string, opts?: ParseOptions) => string | import("./ast.js").Assignment[];
7
7
  };
8
8
  export default _default;
9
9
  export { Lexer, Parser, ParseTargets };
@@ -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;AAGhC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;;sBAGxB,MAAM,QAAQ,YAAY;;AADhD,wBAcC;AAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,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;AAGhC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;;sBAGxB,MAAM,SAAQ,YAAY;;AADhD,wBAcC;AAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,CAAA"}
package/build/index.js CHANGED
@@ -3,7 +3,7 @@ import Parser from './parser.js';
3
3
  import { transform as transformTS } from './transform/ts.js';
4
4
  import { ParseTargets } from './constants.js';
5
5
  export default {
6
- parse: (filePath, opts) => {
6
+ parse: (filePath, opts = { target: ParseTargets.AST }) => {
7
7
  const parser = new Parser(filePath);
8
8
  const ast = parser.parse();
9
9
  if (opts.target === ParseTargets.AST) {
package/build/parser.d.ts CHANGED
@@ -19,6 +19,8 @@ export default class Parser {
19
19
  private openSegment;
20
20
  private parsePropertyName;
21
21
  private parsePropertyType;
22
+ private parseOperator;
23
+ private isOperator;
22
24
  private parsePropertyTypes;
23
25
  private parseOccurrences;
24
26
  /**
@@ -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;AAKjB,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;IA8BxB,OAAO,CAAC,oBAAoB;IAyU5B,OAAO,CAAC,wBAAwB;IAahC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,iBAAiB;IA8GzB,OAAO,CAAC,kBAAkB;IAwC1B,OAAO,CAAC,gBAAgB;IA4DxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAUpB,KAAK;IAuBL,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;gBAEhB,QAAQ,EAAE,MAAM;IAQ7B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,gBAAgB;IA2CxB,OAAO,CAAC,oBAAoB;IA6Y5B,OAAO,CAAC,wBAAwB;IAahC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,iBAAiB;IA6HzB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,kBAAkB;IAiD1B,OAAO,CAAC,gBAAgB;IA4DxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB,KAAK;IAaL,OAAO,CAAC,WAAW;CAKtB"}
package/build/parser.js CHANGED
@@ -6,6 +6,21 @@ import { parseNumberValue } from './utils.js';
6
6
  import { Type } from './ast.js';
7
7
  const NIL_TOKEN = { Type: Tokens.ILLEGAL, Literal: '' };
8
8
  const DEFAULT_OCCURRENCE = { n: 1, m: 1 }; // exactly one time
9
+ const OPERATORS = ['default', 'size', 'regexp', 'bits', 'and', 'within', 'eq', 'ne', 'lt', 'le', 'gt', 'ge'];
10
+ const OPERATORS_EXPECTING_VALUES = {
11
+ default: undefined,
12
+ size: ['literal', 'range'],
13
+ regexp: ['literal'],
14
+ bits: ['group'],
15
+ and: ['group'],
16
+ within: ['group'],
17
+ eq: ['group'],
18
+ ne: ['group'],
19
+ lt: ['group'],
20
+ le: ['group'],
21
+ gt: ['group'],
22
+ ge: ['group'],
23
+ };
9
24
  export default class Parser {
10
25
  #filePath;
11
26
  l;
@@ -23,6 +38,10 @@ export default class Parser {
23
38
  return true;
24
39
  }
25
40
  parseAssignments() {
41
+ const comments = [];
42
+ while (this.curToken.Type === Tokens.COMMENT) {
43
+ comments.push(this.parseComment());
44
+ }
26
45
  /**
27
46
  * expect group identifier, e.g.
28
47
  * groupName =
@@ -45,7 +64,14 @@ export default class Parser {
45
64
  this.nextToken(); // eat `/`
46
65
  }
47
66
  this.nextToken(); // eat `=`
48
- return this.parseAssignmentValue(groupName, isChoiceAddition);
67
+ const assignmentValue = this.parseAssignmentValue(groupName, isChoiceAddition);
68
+ // @ts-expect-error curToken can be changed by now but TS doesn't understand this
69
+ while (this.curToken.Type === Tokens.COMMENT) {
70
+ const comment = this.parseComment();
71
+ comment && comments.push(comment);
72
+ }
73
+ assignmentValue.Comments = comments;
74
+ return assignmentValue;
49
75
  }
50
76
  parseAssignmentValue(groupName, isChoiceAddition = false) {
51
77
  let isChoice = false;
@@ -64,7 +90,8 @@ export default class Parser {
64
90
  Type: 'variable',
65
91
  Name: groupName,
66
92
  IsChoiceAddition: isChoiceAddition,
67
- PropertyType: this.parsePropertyTypes()
93
+ PropertyType: this.parsePropertyTypes(),
94
+ Comments: []
68
95
  };
69
96
  return variable;
70
97
  }
@@ -101,18 +128,46 @@ export default class Parser {
101
128
  Type: 'variable',
102
129
  Name: groupName,
103
130
  IsChoiceAddition: isChoiceAddition,
104
- PropertyType: propertyType
131
+ PropertyType: propertyType,
132
+ Comments: []
105
133
  };
134
+ if (this.isOperator()) {
135
+ variable.Operator = this.parseOperator();
136
+ }
106
137
  return variable;
107
138
  }
108
139
  return propertyType;
109
140
  }
141
+ /**
142
+ * parse operator assignments, e.g. `ip4 = (float .ge 0.0) .default 1.0`
143
+ */
144
+ if (closingTokens.length === 1 && this.peekToken.Type === Tokens.DOT) {
145
+ const prop = {
146
+ Type: this.parsePropertyType(),
147
+ Operator: this.parseOperator()
148
+ };
149
+ this.nextToken(); // eat closing token
150
+ if (groupName) {
151
+ const variable = {
152
+ Type: 'variable',
153
+ Name: groupName,
154
+ IsChoiceAddition: isChoiceAddition,
155
+ PropertyType: prop,
156
+ Operator: this.parseOperator(),
157
+ Comments: []
158
+ };
159
+ return variable;
160
+ }
161
+ return [prop];
162
+ }
110
163
  while (!closingTokens.includes(this.curToken.Type)) {
111
164
  const propertyType = [];
165
+ const comments = [];
112
166
  let isUnwrapped = false;
113
167
  let hasCut = false;
114
168
  let propertyName = '';
115
- let comment = '';
169
+ const leadingComment = this.parseComment(true);
170
+ leadingComment && comments.push(leadingComment);
116
171
  const occurrence = this.parseOccurrences();
117
172
  /**
118
173
  * check if variable name is unwrapped
@@ -145,7 +200,7 @@ export default class Parser {
145
200
  Occurrence: occurrence,
146
201
  Name: '',
147
202
  Type: innerGroup,
148
- Comment: ''
203
+ Comments: []
149
204
  });
150
205
  continue;
151
206
  }
@@ -163,6 +218,7 @@ export default class Parser {
163
218
  if (this.curToken.Type === Tokens.COMMA || closingTokens.includes(this.curToken.Type)) {
164
219
  const tokenType = this.curToken.Type;
165
220
  let parsedComments = false;
221
+ let comment;
166
222
  /**
167
223
  * check if line has a comment
168
224
  */
@@ -182,7 +238,7 @@ export default class Parser {
182
238
  Value: propertyName,
183
239
  Unwrapped: isUnwrapped
184
240
  }],
185
- Comment: comment
241
+ Comments: comment ? [comment] : []
186
242
  });
187
243
  if (this.curToken.Literal === Tokens.COMMA || this.curToken.Literal === closingTokens[0]) {
188
244
  if (this.curToken.Literal === Tokens.COMMA) {
@@ -224,11 +280,11 @@ export default class Parser {
224
280
  Occurrence: occurrence,
225
281
  Name: '',
226
282
  Type: {
227
- Type: "group",
283
+ Type: 'group',
228
284
  Value: propertyName,
229
285
  Unwrapped: isUnwrapped
230
286
  },
231
- Comment: comment
287
+ Comments: comments
232
288
  };
233
289
  if (isChoice) {
234
290
  /**
@@ -258,6 +314,7 @@ export default class Parser {
258
314
  * parse property value
259
315
  */
260
316
  const props = this.parseAssignmentValue();
317
+ const operator = this.isOperator() ? this.parseOperator() : undefined;
261
318
  if (Array.isArray(props)) {
262
319
  /**
263
320
  * property has multiple types (e.g. `float / tstr / int`)
@@ -278,13 +335,15 @@ export default class Parser {
278
335
  flipIsChoice = true;
279
336
  this.nextToken(); // eat ,
280
337
  }
281
- comment = this.parseComment();
338
+ const trailingComment = this.parseComment();
339
+ trailingComment && comments.push(trailingComment);
282
340
  const prop = {
283
341
  HasCut: hasCut,
284
342
  Occurrence: occurrence,
285
343
  Name: propertyName,
286
344
  Type: propertyType,
287
- Comment: comment
345
+ Comments: comments,
346
+ ...(operator ? { Operator: operator } : {})
288
347
  };
289
348
  if (isChoice) {
290
349
  valuesOrProperties[valuesOrProperties.length - 1].push(prop);
@@ -323,9 +382,35 @@ export default class Parser {
323
382
  return {
324
383
  Type: 'array',
325
384
  Name: groupName || '',
326
- Values: valuesOrProperties
385
+ Values: valuesOrProperties,
386
+ Comments: []
327
387
  };
328
388
  }
389
+ /**
390
+ * simplify wrapped types, e.g. from
391
+ * {
392
+ * "Type": "group",
393
+ * "Name": "",
394
+ * "Properties": [
395
+ * {
396
+ * "HasCut": false,
397
+ * "Occurrence": {
398
+ * "n": 1,
399
+ * "m": 1
400
+ * },
401
+ * "Name": "",
402
+ * "Type": "bool",
403
+ * "Comment": ""
404
+ * }
405
+ * ],
406
+ * "IsChoiceAddition": false
407
+ * }
408
+ * back to:
409
+ * bool
410
+ */
411
+ if (!groupName && valuesOrProperties.length === 1 && PREDEFINED_IDENTIFIER.includes(valuesOrProperties[0].Type)) {
412
+ return valuesOrProperties[0].Type;
413
+ }
329
414
  /**
330
415
  * otherwise a group
331
416
  */
@@ -333,7 +418,8 @@ export default class Parser {
333
418
  Type: 'group',
334
419
  Name: groupName || '',
335
420
  Properties: valuesOrProperties,
336
- IsChoiceAddition: isChoiceAddition
421
+ IsChoiceAddition: isChoiceAddition,
422
+ Comments: []
337
423
  };
338
424
  }
339
425
  isPropertyValueSeparator() {
@@ -382,8 +468,9 @@ export default class Parser {
382
468
  throw this.parserError(`Expected property name, received ${this.curToken.Type}(${this.curToken.Literal}), ${this.peekToken.Type}(${this.peekToken.Literal})`);
383
469
  }
384
470
  parsePropertyType() {
385
- let type;
471
+ let type = undefined;
386
472
  let isUnwrapped = false;
473
+ let isGroupedRange = false;
387
474
  /**
388
475
  * check if variable name is unwrapped
389
476
  */
@@ -404,6 +491,8 @@ export default class Parser {
404
491
  case Type.BYTES:
405
492
  case Type.TSTR:
406
493
  case Type.TEXT:
494
+ case Type.NIL:
495
+ case Type.NULL:
407
496
  type = this.curToken.Literal;
408
497
  break;
409
498
  default: {
@@ -451,6 +540,15 @@ export default class Parser {
451
540
  Unwrapped: isUnwrapped
452
541
  };
453
542
  }
543
+ else if (this.curToken.Literal === Tokens.LPAREN && this.peekToken.Type === Tokens.NUMBER) {
544
+ this.nextToken();
545
+ type = {
546
+ Type: 'literal',
547
+ Value: parseNumberValue(this.curToken),
548
+ Unwrapped: isUnwrapped
549
+ };
550
+ isGroupedRange = true;
551
+ }
454
552
  else {
455
553
  throw this.parserError(`Invalid property type "${this.curToken.Literal}"`);
456
554
  }
@@ -484,13 +582,42 @@ export default class Parser {
484
582
  },
485
583
  Unwrapped: isUnwrapped
486
584
  };
585
+ if (isGroupedRange) {
586
+ this.nextToken(); // eat ")"
587
+ }
487
588
  }
488
589
  return type;
489
590
  }
591
+ parseOperator() {
592
+ const type = this.peekToken.Literal;
593
+ if (this.curToken.Literal !== Tokens.DOT || !OPERATORS.includes(this.peekToken.Literal)) {
594
+ throw new Error(`Operator ".${type}", expects a ${OPERATORS_EXPECTING_VALUES[type].join(' or ')} property, but found ${this.peekToken.Literal}!`);
595
+ }
596
+ this.nextToken(); // eat "."
597
+ this.nextToken(); // eat operator type
598
+ const value = this.parsePropertyType();
599
+ this.nextToken(); // eat operator value
600
+ return {
601
+ Type: type,
602
+ Value: value
603
+ };
604
+ }
605
+ isOperator() {
606
+ return this.curToken.Literal === Tokens.DOT && OPERATORS.includes(this.peekToken.Literal);
607
+ }
490
608
  parsePropertyTypes() {
491
609
  const propertyTypes = [];
492
- propertyTypes.push(this.parsePropertyType());
493
- this.nextToken(); // eat `/`
610
+ let prop = this.parsePropertyType();
611
+ if (this.isOperator()) {
612
+ prop = {
613
+ Type: prop,
614
+ Operator: this.parseOperator()
615
+ };
616
+ }
617
+ else {
618
+ this.nextToken(); // eat `/`
619
+ }
620
+ propertyTypes.push(prop);
494
621
  /**
495
622
  * ensure we don't go into the next choice, e.g.:
496
623
  * ```
@@ -576,26 +703,20 @@ export default class Parser {
576
703
  /**
577
704
  * check if line has a comment
578
705
  */
579
- parseComment() {
580
- let comment = '';
581
- if (this.curToken.Type === Tokens.COMMENT) {
582
- comment = this.curToken.Literal.slice(2);
583
- this.nextToken();
706
+ parseComment(isLeading) {
707
+ if (this.curToken.Type !== Tokens.COMMENT) {
708
+ return;
584
709
  }
585
- return comment;
710
+ const comment = this.curToken.Literal.replace(/^;(\s*)/, '');
711
+ this.nextToken();
712
+ if (comment.trim().length === 0) {
713
+ return;
714
+ }
715
+ return { Type: 'comment', Content: comment, Leading: Boolean(isLeading) };
586
716
  }
587
717
  parse() {
588
718
  const definition = [];
589
719
  while (this.curToken.Type !== Tokens.EOF) {
590
- if (this.curToken.Type === Tokens.COMMENT) {
591
- const comment = {
592
- Type: 'comment',
593
- Content: this.curToken.Literal.slice(1).trim()
594
- };
595
- definition.push(comment);
596
- this.nextToken();
597
- continue;
598
- }
599
720
  const group = this.parseAssignments();
600
721
  if (group) {
601
722
  definition.push(group);
@@ -1 +1 @@
1
- {"version":3,"file":"ts.d.ts","sourceRoot":"","sources":["../../src/transform/ts.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAA6C,MAAM,QAAQ,CAAA;AAanF,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,UAoBnD"}
1
+ {"version":3,"file":"ts.d.ts","sourceRoot":"","sources":["../../src/transform/ts.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAmG,MAAM,QAAQ,CAAA;AAmBzI,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,UAkBnD"}
@@ -4,14 +4,17 @@ import typescriptParser from 'recast/parsers/typescript.js';
4
4
  // @ts-ignore
5
5
  import pkg from '../../package.json' assert { type: 'json' };
6
6
  const b = types.builders;
7
- const comments = [];
8
7
  const NATIVE_TYPES = {
9
- number: 'number',
10
- uint: 'Uint32Array',
11
- bool: 'boolean',
12
- str: 'string',
13
- text: 'string',
14
- tstr: 'string'
8
+ number: b.tsNumberKeyword(),
9
+ float: b.tsNumberKeyword(),
10
+ uint: b.tsNumberKeyword(),
11
+ bool: b.tsBooleanKeyword(),
12
+ str: b.tsStringKeyword(),
13
+ text: b.tsStringKeyword(),
14
+ tstr: b.tsStringKeyword(),
15
+ range: b.tsNumberKeyword(),
16
+ nil: b.tsNullKeyword(),
17
+ null: b.tsNullKeyword()
15
18
  };
16
19
  export function transform(assignments) {
17
20
  let ast = parse(`// compiled with https://www.npmjs.com/package/cddl v${pkg.version}`, {
@@ -26,14 +29,9 @@ export function transform(assignments) {
26
29
  }
27
30
  ast.program.body.push(statement);
28
31
  }
29
- ast.program.comments = comments.map((c) => b.commentLine(c, false, false));
30
32
  return print(ast).code;
31
33
  }
32
34
  function parseAssignment(ast, assignment) {
33
- if (assignment.Type === 'comment') {
34
- comments.push(assignment.Content);
35
- return;
36
- }
37
35
  if (assignment.Type === 'variable') {
38
36
  const propType = Array.isArray(assignment.PropertyType)
39
37
  ? assignment.PropertyType
@@ -48,13 +46,18 @@ function parseAssignment(ast, assignment) {
48
46
  typeParameters = b.tsUnionType(propType.map(parsePropertyType));
49
47
  }
50
48
  const expr = b.tsTypeAliasDeclaration(id, typeParameters);
51
- expr.comments = comments.map((c) => b.commentLine(c, true));
49
+ expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
52
50
  return expr;
53
51
  }
54
52
  if (assignment.Type === 'group') {
55
53
  const id = b.identifier(camelcase(assignment.Name, { pascalCase: true }));
56
54
  const objectType = parseObjectType(assignment.Properties);
57
- const expr = b.interfaceDeclaration(id, objectType, []);
55
+ const extendInterfaces = assignment.Properties
56
+ .filter((prop) => prop.Name === '')
57
+ .map((prop) => b.tsExpressionWithTypeArguments(b.identifier(camelcase(prop.Type[0].Value, { pascalCase: true }))));
58
+ const expr = b.tsInterfaceDeclaration(id, b.tsInterfaceBody(objectType));
59
+ expr.extends = extendInterfaces;
60
+ expr.comments = assignment.Comments.map((c) => b.commentLine(` ${c.Content}`, true));
58
61
  return expr;
59
62
  }
60
63
  }
@@ -63,7 +66,7 @@ function parsePropertyType(propType) {
63
66
  return b.tsStringKeyword();
64
67
  }
65
68
  if (propType.Type === 'group') {
66
- return b.tsTypeReference(b.identifier(propType.Value.toString()));
69
+ return b.tsTypeReference(b.identifier(camelcase(propType.Value.toString(), { pascalCase: true })));
67
70
  }
68
71
  if (propType.Type === 'literal') {
69
72
  return b.tsLiteralType(b.stringLiteral(propType.Value.toString()));
@@ -74,40 +77,109 @@ function parseObjectType(props) {
74
77
  const propItems = [];
75
78
  for (const prop of props) {
76
79
  /**
77
- * ToDo(Christian): support Extensible
80
+ * Empty groups like
81
+ * {
82
+ * HasCut: false,
83
+ * Occurrence: { n: 1, m: 1 },
84
+ * Name: '',
85
+ * Type: [ { Type: 'group', Value: 'Extensible', Unwrapped: false } ],
86
+ * Comment: ''
87
+ * }
88
+ * are ignored and later added as interface extensions
78
89
  */
79
90
  if (prop.Name === '') {
80
- propItems[propItems.length - 1].comments = [b.commentLine(`Missing: ${JSON.stringify(prop)}`)];
81
91
  continue;
82
92
  }
83
93
  const id = b.identifier(camelcase(prop.Name));
84
94
  const cddlType = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
85
- const typeParameters = b.unionTypeAnnotation(cddlType.map((t) => {
86
- if (typeof t === 'string') {
87
- if (!NATIVE_TYPES[t]) {
88
- throw new Error(`Unknown native type: "${t}`);
89
- }
90
- return b.typeParameter(NATIVE_TYPES[t]);
91
- }
92
- else if (t.Value === 'null') {
93
- return b.nullTypeAnnotation();
94
- }
95
- else if (t.Type === 'group') {
96
- const value = t.Value;
97
- return b.typeParameter(
98
- /**
99
- * transform native CDDL types into TypeScript types
100
- */
101
- NATIVE_TYPES[value] ? value : camelcase(value.toString(), { pascalCase: true }));
102
- }
103
- else if (t.Type === 'literal' && typeof t.Value === 'string') {
104
- return b.stringLiteralTypeAnnotation(t.Value, t.Value);
95
+ const comments = prop.Comments.map((c) => ` ${c.Content}`);
96
+ if (prop.Operator && prop.Operator.Type === 'default') {
97
+ const defaultValue = parseDefaultValue(prop.Operator);
98
+ defaultValue && comments.length && comments.push(''); // add empty line if we have previous comments
99
+ defaultValue && comments.push(` @default ${defaultValue}`);
100
+ }
101
+ const type = cddlType.map((t) => {
102
+ const unionType = parseUnionType(t);
103
+ if (unionType) {
104
+ const defaultValue = parseDefaultValue(t.Operator);
105
+ defaultValue && comments.length && comments.push(''); // add empty line if we have previous comments
106
+ defaultValue && comments.push(` @default ${defaultValue}`);
107
+ return unionType;
105
108
  }
106
109
  throw new Error(`Couldn't parse property ${JSON.stringify(t)}`);
107
- }));
110
+ });
111
+ const typeAnnotation = b.tsTypeAnnotation(b.tsUnionType(type));
108
112
  const isOptional = prop.Occurrence.n === 0;
109
- propItems.push(b.objectTypeProperty(id, typeParameters, isOptional));
113
+ const propSignature = b.tsPropertySignature(id, typeAnnotation, isOptional);
114
+ propSignature.comments = comments.length ? [b.commentBlock(`*\n *${comments.join('\n *')}\n `)] : [];
115
+ propItems.push(propSignature);
116
+ }
117
+ return propItems;
118
+ }
119
+ function parseUnionType(t) {
120
+ if (typeof t === 'string') {
121
+ if (!NATIVE_TYPES[t]) {
122
+ throw new Error(`Unknown native type: "${t}`);
123
+ }
124
+ return NATIVE_TYPES[t];
125
+ }
126
+ else if (NATIVE_TYPES[t.Type]) {
127
+ return NATIVE_TYPES[t.Type];
128
+ }
129
+ else if (t.Value === 'null') {
130
+ return b.tsNullKeyword();
131
+ }
132
+ else if (t.Type === 'group') {
133
+ const value = t.Value;
134
+ /**
135
+ * check if we have
136
+ * ?attributes: {*text => text},
137
+ */
138
+ if (!value && t.Properties) {
139
+ return b.tsTypeLiteral(parseObjectType(t.Properties));
140
+ }
141
+ return b.tsTypeReference(b.identifier(camelcase(value.toString(), { pascalCase: true })));
142
+ }
143
+ else if (t.Type === 'literal' && typeof t.Value === 'string') {
144
+ return b.tsLiteralType(b.stringLiteral(t.Value));
145
+ }
146
+ else if (t.Type === 'literal' && typeof t.Value === 'number') {
147
+ return b.tsLiteralType(b.numericLiteral(t.Value));
148
+ }
149
+ else if (t.Type === 'array') {
150
+ const types = t.Values[0].Type;
151
+ const typedTypes = (Array.isArray(types) ? types : [types]).map((val) => {
152
+ return typeof val === 'string' && NATIVE_TYPES[val]
153
+ ? NATIVE_TYPES[val]
154
+ : b.tsTypeReference(b.identifier(camelcase(val.Value, { pascalCase: true })));
155
+ });
156
+ return b.tsArrayType(typedTypes.length > 1
157
+ ? b.tsParenthesizedType(b.tsUnionType(typedTypes))
158
+ : b.tsUnionType(typedTypes));
159
+ }
160
+ else if (typeof t.Type === 'object' && t.Type.Type === 'range') {
161
+ return b.tsNumberKeyword();
162
+ }
163
+ else if (typeof t.Type === 'object' && t.Type.Type === 'group') {
164
+ /**
165
+ * e.g. ?pointerType: input.PointerType .default "mouse"
166
+ */
167
+ const referenceValue = camelcase(t.Type.Value, { pascalCase: true });
168
+ return b.tsTypeReference(b.identifier(referenceValue));
169
+ }
170
+ }
171
+ function parseDefaultValue(operator) {
172
+ if (!operator || operator.Type !== 'default') {
173
+ return;
174
+ }
175
+ const operatorValue = operator.Value;
176
+ if (operator.Value === 'null') {
177
+ return operator.Value;
178
+ }
179
+ if (operatorValue.Type !== 'literal') {
180
+ throw new Error(`Can't parse operator default value of ${JSON.stringify(operator)}`);
110
181
  }
111
- const obj = b.objectTypeAnnotation(propItems);
112
- return obj;
182
+ return typeof operatorValue.Value === 'string'
183
+ ? `'${operatorValue.Value}'`
184
+ : operatorValue.Value;
113
185
  }
@@ -227,7 +227,7 @@ ScriptCommand = (
227
227
  script.CallFunction //
228
228
  script.Disown //
229
229
  script.Evaluate //
230
- script.GetRealms
230
+ script.GetRealms //
231
231
  script.RemovePreloadScriptCommand
232
232
  )
233
233
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cddl",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Concise data definition language (RFC 8610) implementation and JSON validator in Node.js",
5
5
  "author": "Christian Bromann <christian@saucelabs.com>",
6
6
  "license": "MIT",