cddl 0.13.0 → 0.14.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.
@@ -0,0 +1,12 @@
1
+ {
2
+ "plugins": {
3
+ "release-it-pnpm": {}
4
+ },
5
+ "git": {
6
+ "requireCleanWorkingDir": false,
7
+ "addUntrackedFiles": true,
8
+ "tagName": "cddl-v${version}",
9
+ "commitMessage": "chore(cddl): release v${version}",
10
+ "changelog": "git log --pretty=format:\"* %s (%h)\" ${latestTag ? latestTag + '..HEAD' : ''} -- . ../../tsconfig.json"
11
+ }
12
+ }
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020 Christian Bromann
3
+ Copyright (c) 2026 Christian Bromann
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- CDDL [![Test](https://github.com/christian-bromann/cddl/actions/workflows/test.yml/badge.svg)](https://github.com/christian-bromann/cddl/actions/workflows/test.yml)
1
+ CDDL
2
2
  ====
3
3
 
4
4
  > Concise data definition language ([RFC 8610](https://tools.ietf.org/html/rfc8610)) implementation and JSON validator in Node.js.
@@ -10,7 +10,7 @@ There are also CDDL parsers for other languages:
10
10
 
11
11
  The package is currently mostly used to help generate typed interfaces for the WebDriver Bidi specification in the following projects:
12
12
  - [WebdriverIO](https://webdriver.io) - via the [`cddl2ts`](https://www.npmjs.com/package/cddl2ts) package and [this script](https://github.com/webdriverio/webdriverio/blob/a2ae35332f9b3fc9490136df1ac3d2e14c1e35b6/scripts/bidi/index.ts)
13
- - [Selenium](https://selenium.dev) - via the [`cddl2java`](https://github.com/christian-bromann/cddl2java) package
13
+ - [Selenium](https://selenium.dev) - via the [`cddl2java`](https://github.com/webdriverio/cddl2java) package
14
14
 
15
15
  __Note:__ this is __work in progress__, feel free to have a look at the code or contribute but don't use this for anything yet!
16
16
 
@@ -66,8 +66,8 @@ console.log(ast)
66
66
  */
67
67
  ```
68
68
 
69
- Read the full documentation on this AST in the [`/docs`](/docs/README.md) directory.
69
+ Read the full documentation on this AST in the [`docs`](./docs/README.md) directory.
70
70
 
71
71
  ---
72
72
 
73
- If you are interested in this project, please feel free to contribute ideas or code patches. Have a look at our [contributing guidelines](https://github.com/christian-bromann/cddl/blob/master/CONTRIBUTING.md) to get started.
73
+ If you are interested in this project, please feel free to contribute ideas or code patches. Have a look at our [contributing guidelines](https://github.com/webdriverio/cddl/blob/master/CONTRIBUTING.md) to get started.
@@ -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;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"}
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"}
package/build/parser.js CHANGED
@@ -120,7 +120,7 @@ export default class Parser {
120
120
  const propertyType = [];
121
121
  while (!closingTokens.includes(this.curToken.Type)) {
122
122
  propertyType.push(...this.parsePropertyTypes());
123
- if (this.curToken.Type === Tokens.RPAREN) {
123
+ if (closingTokens.includes(this.curToken.Type)) {
124
124
  this.nextToken();
125
125
  break;
126
126
  }
@@ -239,6 +239,14 @@ export default class Parser {
239
239
  this.nextToken();
240
240
  isChoice = false;
241
241
  }
242
+ if (this.curToken.Type === Tokens.SLASH && this.peekToken.Type !== Tokens.SLASH) {
243
+ if (!isChoice) {
244
+ const last = valuesOrProperties.pop();
245
+ valuesOrProperties.push([last]);
246
+ isChoice = true;
247
+ }
248
+ this.nextToken();
249
+ }
242
250
  continue;
243
251
  }
244
252
  /**
@@ -727,8 +735,8 @@ export default class Parser {
727
735
  Operator: this.parseOperator()
728
736
  };
729
737
  }
730
- else {
731
- this.nextToken(); // eat `/`
738
+ else if (this.curToken.Type !== Tokens.SLASH) {
739
+ this.nextToken(); // eat Property if not already consumed (e.g. by Group parsing)
732
740
  }
733
741
  propertyTypes.push(prop);
734
742
  /**
@@ -747,7 +755,7 @@ export default class Parser {
747
755
  while (this.curToken.Type === Tokens.SLASH) {
748
756
  this.nextToken(); // eat `/`
749
757
  propertyTypes.push(this.parsePropertyType());
750
- if (!this.isOperator()) {
758
+ if (!this.isOperator() && this.curToken.Type !== Tokens.SLASH) {
751
759
  /**
752
760
  * If we are not parsing an operator, we need to eat the next token;
753
761
  * otherwise, the operator will be parsed by the caller
@@ -193,4 +193,39 @@ LiteralTest = {
193
193
  is_active: true,
194
194
  deleted_at: null
195
195
  }
196
+
197
+ ---
198
+
199
+ ## 6. Groups as Maps
200
+
201
+ Wrap a **Group** in curly braces `{}` to treat it as a **Map**.
202
+
203
+ ```cddl
204
+ ; A reusable Group of fields
205
+ DateFields = ( year: int, month: int, day: int )
206
+
207
+ ; 1. Mix it into a Map
208
+ Appointment = {
209
+ description: tstr,
210
+ DateFields
211
+ }
212
+
213
+ ; 2. Standalone Map
214
+ DateObject = { DateFields }
215
+ ```
216
+
217
+ ### Choices with Groups and Maps
218
+
219
+ You can mix explicit Maps and Groups-as-Maps in type choices:
220
+
221
+ ```cddl
222
+ ArrayMap = { type: "array", values: [...] }
223
+ DateGroup = ( type: "date", value: tstr )
224
+
225
+ ; Matches either map structure
226
+ MixedValue = (
227
+ ArrayMap / ; Map defined elsewhere
228
+ { DateGroup } ; Group wrapped in Map
229
+ )
230
+ ```
196
231
  ```
package/package.json CHANGED
@@ -1,23 +1,19 @@
1
1
  {
2
2
  "name": "cddl",
3
- "version": "0.13.0",
3
+ "version": "0.14.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",
7
- "homepage": "https://github.com/christian-bromann/cddl#readme",
7
+ "homepage": "https://github.com/webdriverio/cddl#readme",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+ssh://git@github.com/christian-bromann/cddl.git"
10
+ "url": "git+ssh://git@github.com/webdriverio/cddl.git"
11
11
  },
12
12
  "keywords": [
13
13
  "cddl"
14
14
  ],
15
- "engines": {
16
- "node": ">=20.0.0",
17
- "npm": ">=9.0.0"
18
- },
19
15
  "bugs": {
20
- "url": "https://github.com/christian-bromann/cddl/issues"
16
+ "url": "https://github.com/webdriverio/cddl/issues"
21
17
  },
22
18
  "type": "module",
23
19
  "exports": "./build/index.js",
@@ -25,29 +21,19 @@
25
21
  "bin": {
26
22
  "cddl": "./bin/cddl.js"
27
23
  },
28
- "scripts": {
29
- "build": "run-s clean compile",
30
- "clean": "rm -rf ./build ./coverage",
31
- "compile": "tsc -p ./tsconfig.json",
32
- "release": "release-it --github.release",
33
- "release:ci": "npm run release -- --ci --npm.skipChecks --no-git.requireCleanWorkingDir",
34
- "release:patch": "npm run release -- patch",
35
- "release:minor": "npm run release -- minor",
36
- "release:major": "npm run release -- major",
37
- "test": "vitest run",
38
- "checks:all": "npm run compile && npm run test",
39
- "watch": "tsc --watch"
40
- },
41
24
  "devDependencies": {
42
- "@types/node": "^24.12.0",
25
+ "@types/node": "^25.5.0",
43
26
  "@types/yargs": "^17.0.35",
44
27
  "@vitest/coverage-v8": "^4.1.0",
45
- "npm-run-all": "^4.1.5",
46
- "release-it": "^19.2.4",
47
- "typescript": "^5.9.3",
28
+ "npm-run-all2": "^8.0.4",
29
+ "typescript": "^6.0.2",
48
30
  "vitest": "^4.1.0"
49
31
  },
50
32
  "dependencies": {
51
33
  "yargs": "^18.0.0"
34
+ },
35
+ "scripts": {
36
+ "release": "release-it --github.release",
37
+ "release:ci": "pnpm release --ci --npm.skipChecks"
52
38
  }
53
- }
39
+ }
package/src/ast.ts ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * a group definition
3
+ * ```
4
+ * person = {
5
+ * age: int,
6
+ * name: tstr,
7
+ * employer: tstr,
8
+ * }
9
+ * ```
10
+ */
11
+ export type Group = {
12
+ Type: 'group'
13
+ Name: string
14
+ IsChoiceAddition: boolean
15
+ Properties: (Property|Property[])[]
16
+ Comments: Comment[]
17
+ }
18
+
19
+ /**
20
+ * an array definition
21
+ * ```
22
+ * Geography = [
23
+ * city: tstr
24
+ * ]
25
+ * ```
26
+ */
27
+ export type Array = {
28
+ Type: 'array'
29
+ Name: string
30
+ Values: (Property|Property[])[]
31
+ Comments: Comment[]
32
+ }
33
+
34
+ /**
35
+ * a tag definition
36
+ * ```
37
+ * #6.32(tstr)
38
+ * ```
39
+ */
40
+ export type Tag = {
41
+ NumericPart: number
42
+ TypePart: string
43
+ }
44
+
45
+ /**
46
+ * a variable assignment
47
+ * ```
48
+ * device-address = byte
49
+ * ```
50
+ */
51
+ export type Variable = {
52
+ Type: 'variable'
53
+ Name: string
54
+ IsChoiceAddition: boolean
55
+ PropertyType: PropertyType | PropertyType[]
56
+ Operator?: Operator
57
+ Comments: Comment[]
58
+ }
59
+
60
+ /**
61
+ * a comment statement
62
+ * ```
63
+ * ; This is a comment
64
+ * ```
65
+ */
66
+ export type Comment = {
67
+ Type: 'comment'
68
+ Content: string
69
+ Leading: boolean
70
+ }
71
+
72
+ export type Occurrence = {
73
+ n: number
74
+ m: number
75
+ }
76
+
77
+ export type Property = {
78
+ HasCut: boolean
79
+ Occurrence: Occurrence
80
+ Name: PropertyName
81
+ Type: PropertyType | PropertyType[]
82
+ Comments: Comment[]
83
+ Operator?: Operator
84
+ }
85
+
86
+ export enum Type {
87
+ /**
88
+ * any types
89
+ */
90
+ ANY = 'any',
91
+
92
+ /**
93
+ * boolean types
94
+ */
95
+ // Boolean value (major type 7, additional information 20 or 21).
96
+ BOOL = 'bool',
97
+
98
+ /**
99
+ * numeric types
100
+ */
101
+ // An unsigned integer or a negative integer.
102
+ INT = 'int',
103
+ // An unsigned integer (major type 0).
104
+ UINT = 'uint',
105
+ // A negative integer (major type 1).
106
+ NINT = 'nint',
107
+ // One of float16, float32, or float64.
108
+ FLOAT = 'float',
109
+ // A number representable as an IEEE 754 half-precision float
110
+ FLOAT16 = 'float16',
111
+ // A number representable as an IEEE 754 single-precision
112
+ FLOAT32 = 'float32',
113
+ // A number representable as an IEEE 754 double-precision
114
+ FLOAT64 = 'float64',
115
+
116
+ /**
117
+ * string types
118
+ */
119
+ // A byte string (major type 2).
120
+ BSTR = 'bstr',
121
+ // A byte string (major type 2).
122
+ BYTES = 'bytes',
123
+ // Text string (major type 3)
124
+ TSTR = 'tstr',
125
+ // Text string (major type 3)
126
+ TEXT = 'text',
127
+
128
+ /**
129
+ * null types
130
+ */
131
+ NIL = 'nil',
132
+ NULL = 'null'
133
+ }
134
+
135
+ /**
136
+ * can be a number, e.g. "foo = 0..10"
137
+ * ```
138
+ * {
139
+ * Type: "int",
140
+ * Value: 6
141
+ * }
142
+ * ```
143
+ * or a literal, e.g. "foo = 0..max-byte"
144
+ * ```
145
+ * {
146
+ * Type: "literal",
147
+ * Value: "max-byte"
148
+ * }
149
+ * ```
150
+ */
151
+ export type RangePropertyReference = number | string
152
+
153
+ export type Range = {
154
+ Min: RangePropertyReference
155
+ Max: RangePropertyReference
156
+ Inclusive: boolean
157
+ }
158
+
159
+ export type OperatorType = 'default' | 'size' | 'regexp' | 'bits' | 'and' | 'within' | 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge'
160
+ export interface Operator {
161
+ Type: OperatorType
162
+ Value: PropertyType
163
+ }
164
+
165
+ export type PropertyReferenceType = 'literal' | 'group' | 'group_array' | 'array' | 'range' | 'tag'
166
+ export type PropertyReference = {
167
+ Type: PropertyReferenceType
168
+ Value: string | number | boolean | Group | Array | Range | Tag
169
+ Unwrapped: boolean
170
+ Operator?: Operator
171
+ }
172
+
173
+ export interface NativeTypeWithOperator {
174
+ Type: Type | PropertyReference
175
+ Operator?: Operator
176
+ }
177
+
178
+ export type Assignment = Group | Array | Variable
179
+ export type PropertyType = Assignment | Array | PropertyReference | string | NativeTypeWithOperator
180
+ export type PropertyName = string
@@ -0,0 +1,33 @@
1
+ import repl from 'node:repl'
2
+ import type { Argv } from 'yargs'
3
+
4
+ import { CLI_EPILOGUE } from '../constants.js'
5
+ import Lexer from '../../lexer.js'
6
+ import { Tokens } from '../../tokens.js'
7
+
8
+ export const command = 'repl'
9
+ export const desc = 'Run CDDL repl'
10
+ export const builder = (yargs: Argv<{}>) => {
11
+ return yargs
12
+ .epilogue(CLI_EPILOGUE)
13
+ .help()
14
+ }
15
+
16
+ export const handler = () => {
17
+ repl.start({
18
+ prompt: '> ',
19
+ eval: evaluate
20
+ })
21
+ }
22
+
23
+ export function evaluate (evalCmd: string, _: any, _file: string, callback: (err: Error | null, result: any) => void) {
24
+ if (!evalCmd) {
25
+ return callback(new Error('No input'), null)
26
+ }
27
+
28
+ const l = new Lexer(evalCmd)
29
+ for (let tok = l.nextToken(); tok.Type !== Tokens.EOF; tok = l.nextToken()) {
30
+ console.log(tok)
31
+ }
32
+ return callback(null, null)
33
+ }
@@ -0,0 +1,44 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import type { Argv, ArgumentsCamelCase } from 'yargs'
4
+
5
+ import { CLI_EPILOGUE } from '../constants.js'
6
+ import { parse } from '../../index.js'
7
+
8
+ interface ValidateArguments {
9
+ filePath: string
10
+ }
11
+
12
+ export const command = 'validate <filePath>'
13
+ export const desc = 'Validate a *.cddl file'
14
+ export const builder = (yargs: Argv<{}>) => {
15
+ return yargs
16
+ .epilogue(CLI_EPILOGUE)
17
+ .help()
18
+ }
19
+
20
+ export const handler = (argv: ArgumentsCamelCase<ValidateArguments>) => {
21
+ const filePath = argv.filePath.startsWith('/')
22
+ ? argv.filePath
23
+ : path.resolve(process.cwd(), argv.filePath)
24
+
25
+ if (!fs.existsSync(filePath)) {
26
+ console.error(`Couldn't find CDDL file at ${filePath}`)
27
+ return process.exit(1)
28
+ }
29
+
30
+ try {
31
+ parse(filePath)
32
+
33
+
34
+ /**
35
+ * ToDo check for
36
+ * - missing group declarations
37
+ */
38
+
39
+ console.log('✅ Valid CDDL file!')
40
+ } catch (err: unknown) {
41
+ console.error(`⚠️ Invalid CDDL file (${filePath})\n\n${(err as Error).stack}`)
42
+ process.exit(1)
43
+ }
44
+ }
@@ -0,0 +1 @@
1
+ export const CLI_EPILOGUE = `Copyright 2023 - Christian Bromann`
@@ -0,0 +1,18 @@
1
+ import yargs from 'yargs/yargs'
2
+ import { hideBin } from 'yargs/helpers'
3
+
4
+ import * as replCommand from './commands/repl.js'
5
+ import * as validateCommand from './commands/validate.js'
6
+ import { CLI_EPILOGUE } from './constants.js'
7
+
8
+ export default async function () {
9
+ const argv = yargs(hideBin(process.argv))
10
+ .command(replCommand)
11
+ .command(validateCommand as any)
12
+ .example('$0 repl', 'Start CDDL repl')
13
+ .epilogue(CLI_EPILOGUE)
14
+ .demandCommand()
15
+ .help()
16
+
17
+ return argv.argv
18
+ }
@@ -0,0 +1,16 @@
1
+ export const WHITESPACE_CHARACTERS = [' ', '\t', '\n', '\r']
2
+ export const BOOLEAN_LITERALS = ['true', 'false']
3
+
4
+ /**
5
+ * as defined in Appendix D
6
+ * https://tools.ietf.org/html/draft-ietf-cbor-cddl-08#appendix-D
7
+ */
8
+ export const PREDEFINED_IDENTIFIER = [
9
+ 'any', 'uint', 'nint', 'int', 'bstr', 'bytes', 'tstr', 'text',
10
+ 'tdate', 'time', 'number', 'biguint', 'bignint', 'bigint',
11
+ 'integer', 'unsigned', 'decfrac', 'bigfloat', 'eb64url',
12
+ 'eb64legacy', 'eb16', 'encoded-cbor', 'uri', 'b64url',
13
+ 'b64legacy', 'regexp', 'mime-message', 'cbor-any', 'float16',
14
+ 'float32', 'float64', 'float16-32', 'float32-64', 'float',
15
+ 'false', 'true', 'bool', 'nil', 'null', 'undefined'
16
+ ]
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ import Lexer from './lexer.js'
2
+ import Parser from './parser.js'
3
+
4
+ export function parse (filePath: string) {
5
+ const parser = new Parser(filePath)
6
+ return parser.parse()
7
+ }
8
+
9
+ export default { parse }
10
+ export { Lexer, Parser }
11
+ export * from './ast.js'