-
Notifications
You must be signed in to change notification settings - Fork 13
/
diagnostic.js
154 lines (144 loc) · 4.71 KB
/
diagnostic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { Tokeniser } from './decode.js'
import { toHex, fromHex } from './byte-utils.js'
import { uintBoundaries } from './0uint.js'
const utf8Encoder = new TextEncoder()
const utf8Decoder = new TextDecoder()
/**
* @param {Uint8Array} inp
* @param {number} [width]
*/
function * tokensToDiagnostic (inp, width = 100) {
const tokeniser = new Tokeniser(inp, { retainStringBytes: true, allowBigInt: true })
let pos = 0
const indent = []
/**
* @param {number} start
* @param {number} length
* @returns {string}
*/
const slc = (start, length) => {
return toHex(inp.slice(pos + start, pos + start + length))
}
while (!tokeniser.done()) {
const token = tokeniser.next()
let margin = ''.padStart(indent.length * 2, ' ')
// @ts-ignore should be safe for decode
let vLength = token.encodedLength - 1
/** @type {string|number} */
let v = String(token.value)
let outp = `${margin}${slc(0, 1)}`
const str = token.type.name === 'bytes' || token.type.name === 'string'
if (token.type.name === 'string') {
v = v.length
vLength -= v
} else if (token.type.name === 'bytes') {
v = token.value.length
// @ts-ignore
vLength -= v
}
let multilen
switch (token.type.name) {
case 'string':
case 'bytes':
case 'map':
case 'array':
// for bytes and string, we want to print out the length part of the value prefix if it
// exists - it exists for short lengths (<24) but does for longer lengths
multilen = token.type.name === 'string' ? utf8Encoder.encode(token.value).length : token.value.length
if (multilen >= uintBoundaries[0]) {
if (multilen < uintBoundaries[1]) {
outp += ` ${slc(1, 1)}`
} else if (multilen < uintBoundaries[2]) {
outp += ` ${slc(1, 2)}`
/* c8 ignore next 5 */
} else if (multilen < uintBoundaries[3]) { // sus
outp += ` ${slc(1, 4)}`
} else if (multilen < uintBoundaries[4]) { // orly?
outp += ` ${slc(1, 8)}`
}
}
break
default:
// print the value if it's not compacted into the first byte
outp += ` ${slc(1, vLength)}`
break
}
outp = outp.padEnd(width / 2, ' ')
outp += `# ${margin}${token.type.name}`
if (token.type.name !== v) {
outp += `(${v})`
}
yield outp
if (str) {
let asString = token.type.name === 'string'
margin += ' '
let repr = asString ? utf8Encoder.encode(token.value) : token.value
if (asString && token.byteValue !== undefined) {
if (repr.length !== token.byteValue.length) {
// bail on printing this as a string, it's probably not utf8, so treat it as bytes
// (you can probably blame a Go programmer for this)
repr = token.byteValue
asString = false
}
}
const wh = ((width / 2) - margin.length - 1) / 2
let snip = 0
while (repr.length - snip > 0) {
const piece = repr.slice(snip, snip + wh)
snip += piece.length
const st = asString
? utf8Decoder.decode(piece)
: piece.reduce((/** @type {string} */ p, /** @type {number} */ c) => {
if (c < 0x20 || (c >= 0x7f && c < 0xa1) || c === 0xad) {
return `${p}\\x${c.toString(16).padStart(2, '0')}`
}
return `${p}${String.fromCharCode(c)}`
}, '')
yield `${margin}${toHex(piece)}`.padEnd(width / 2, ' ') + `# ${margin}"${st}"`
}
}
if (indent.length) {
indent[indent.length - 1]--
}
if (!token.type.terminal) {
switch (token.type.name) {
case 'map':
indent.push(token.value * 2)
break
case 'array':
indent.push(token.value)
break
// TODO: test tags .. somehow
/* c8 ignore next 5 */
case 'tag':
indent.push(1)
break
default:
throw new Error(`Unknown token type '${token.type.name}'`)
}
}
while (indent.length && indent[indent.length - 1] <= 0) {
indent.pop()
}
// @ts-ignore it should be set on a decode operation
pos += token.encodedLength
}
}
/**
* Convert an input string formatted as CBOR diagnostic output into binary CBOR form.
* @param {string} input
* @returns {Uint8Array}
*/
function fromDiag (input) {
/* c8 ignore next 3 */
if (typeof input !== 'string') {
throw new TypeError('Expected string input')
}
input = input.replace(/#.*?$/mg, '').replace(/[\s\r\n]+/mg, '')
/* c8 ignore next 3 */
if (/[^a-f0-9]/i.test(input)) {
throw new TypeError('Input string was not CBOR diagnostic format')
}
return fromHex(input)
}
export { tokensToDiagnostic, fromDiag }