-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathdiagnostic.js
More file actions
157 lines (147 loc) · 4.78 KB
/
Copy pathdiagnostic.js
File metadata and controls
157 lines (147 loc) · 4.78 KB
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
155
156
157
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':
// print the length bytes if they exist (length >= 24)
// for string/bytes, token.value is the content; for array/map, token.value IS the count
if (token.type.name === 'string') {
multilen = utf8Encoder.encode(token.value).length
} else if (token.type.name === 'bytes') {
multilen = token.value.length
} else {
// array or map - value is the count directly
multilen = token.value
}
if (multilen >= uintBoundaries[0]) {
if (multilen < uintBoundaries[1]) {
outp += ` ${slc(1, 1)}`
} else if (multilen < uintBoundaries[2]) {
outp += ` ${slc(1, 2)}`
} 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
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) {
if (typeof input !== 'string') {
throw new TypeError('Expected string input')
}
input = input.replace(/#.*?$/mg, '').replace(/[\s\r\n]+/mg, '')
if (/[^a-f0-9]/i.test(input)) {
throw new TypeError('Input string was not CBOR diagnostic format')
}
return fromHex(input)
}
export { tokensToDiagnostic, fromDiag }