@helios-lang/effect 0.1.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.
Files changed (64) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +3 -0
  3. package/dist/Address.js +13 -0
  4. package/dist/Address.js.map +1 -0
  5. package/dist/Bech32.js +153 -0
  6. package/dist/Bech32.js.map +1 -0
  7. package/dist/Cbor.js +1171 -0
  8. package/dist/Cbor.js.map +1 -0
  9. package/dist/Uplc/Cek.js +3 -0
  10. package/dist/Uplc/Cek.js.map +1 -0
  11. package/dist/Uplc/Data.js +171 -0
  12. package/dist/Uplc/Data.js.map +1 -0
  13. package/dist/Uplc/DataSchema.js +118 -0
  14. package/dist/Uplc/DataSchema.js.map +1 -0
  15. package/dist/Uplc/Primitive.js +23 -0
  16. package/dist/Uplc/Primitive.js.map +1 -0
  17. package/dist/Uplc/index.js +4 -0
  18. package/dist/Uplc/index.js.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/internal/Base32.js +201 -0
  22. package/dist/internal/Base32.js.map +1 -0
  23. package/dist/internal/BigEndian.js +56 -0
  24. package/dist/internal/BigEndian.js.map +1 -0
  25. package/dist/internal/Bits.js +300 -0
  26. package/dist/internal/Bits.js.map +1 -0
  27. package/dist/internal/Bytes.js +293 -0
  28. package/dist/internal/Bytes.js.map +1 -0
  29. package/dist/internal/Flat.js +298 -0
  30. package/dist/internal/Flat.js.map +1 -0
  31. package/dist/internal/Float.js +154 -0
  32. package/dist/internal/Float.js.map +1 -0
  33. package/dist/internal/Utf8.js +44 -0
  34. package/dist/internal/Utf8.js.map +1 -0
  35. package/eslint.config.mjs +30 -0
  36. package/package.json +36 -0
  37. package/src/Address.ts +20 -0
  38. package/src/Bech32.test.ts +117 -0
  39. package/src/Bech32.ts +198 -0
  40. package/src/Cbor.test.ts +1610 -0
  41. package/src/Cbor.ts +1704 -0
  42. package/src/Uplc/Cek.ts +92 -0
  43. package/src/Uplc/Data.ts +259 -0
  44. package/src/Uplc/DataSchema.test.ts +207 -0
  45. package/src/Uplc/DataSchema.ts +181 -0
  46. package/src/Uplc/Primitive.ts +56 -0
  47. package/src/Uplc/index.ts +3 -0
  48. package/src/index.ts +4 -0
  49. package/src/internal/Base32.test.ts +219 -0
  50. package/src/internal/Base32.ts +341 -0
  51. package/src/internal/BigEndian.test.ts +79 -0
  52. package/src/internal/BigEndian.ts +67 -0
  53. package/src/internal/Bits.test.ts +300 -0
  54. package/src/internal/Bits.ts +398 -0
  55. package/src/internal/Bytes.test.ts +369 -0
  56. package/src/internal/Bytes.ts +343 -0
  57. package/src/internal/Flat.test.ts +29 -0
  58. package/src/internal/Flat.ts +387 -0
  59. package/src/internal/Float.test.ts +51 -0
  60. package/src/internal/Float.ts +190 -0
  61. package/src/internal/Utf8.test.ts +69 -0
  62. package/src/internal/Utf8.ts +58 -0
  63. package/tsconfig.build.json +14 -0
  64. package/tsconfig.json +38 -0
@@ -0,0 +1,300 @@
1
+ import { describe, expect, it } from "bun:test"
2
+ import { Either } from "effect"
3
+ import * as Bits from "./Bits.js"
4
+
5
+ describe("Bits.Reader", () => {
6
+ describe("initialized with [255]", () => {
7
+ const bytes = [255]
8
+
9
+ it("returns 7 when reading the first 3 bits", () => {
10
+ const r = Bits.makeReader(bytes)
11
+ expect(r.readBits(3)).toBe(7)
12
+ })
13
+
14
+ it("is NOT at EOF when reading the first 7 bits", () => {
15
+ const r = Bits.makeReader(bytes)
16
+ r.readBits(7)
17
+ expect(r.isAtEnd()).toBe(false)
18
+ })
19
+
20
+ it("is at EOF when reading the first 8 bits", () => {
21
+ const r = Bits.makeReader(bytes)
22
+ r.readBits(8)
23
+ expect(r.isAtEnd()).toBe(true)
24
+ })
25
+
26
+ it("fails when reading 9 bits", () => {
27
+ const r = Bits.makeReader(bytes)
28
+ expect(() => r.readBits(9)).toThrow()
29
+ })
30
+ })
31
+
32
+ describe("initialized with an empty Uint8Array", () => {
33
+ it(`fails when reading a single bit`, () => {
34
+ const r = Bits.makeReader(new Uint8Array(0))
35
+ expect(() => r.readBits(1)).toThrow()
36
+ })
37
+
38
+ it(`fails when reading a single byte`, () => {
39
+ const r = Bits.makeReader(new Uint8Array(0))
40
+ expect(() => r.readByte()).toThrow()
41
+ })
42
+ })
43
+
44
+ describe("initialized with [255, 255] and truncate set to false", () => {
45
+ const bytes = [255, 255]
46
+
47
+ describe("discard 14 bits", () => {
48
+ it("returns 3 when reading 2 bits", () => {
49
+ const r = Bits.makeReader(bytes, false)
50
+ r.readBits(7)
51
+ r.readBits(7)
52
+ expect(r.readBits(2)).toBe(3)
53
+ })
54
+
55
+ it("returns 0b11000000 when reading 8 bits", () => {
56
+ const r = Bits.makeReader(bytes, false)
57
+ r.readBits(7)
58
+ r.readBits(7)
59
+ expect(r.readBits(8)).toBe(0b11000000)
60
+ })
61
+ })
62
+ })
63
+
64
+ describe("initialized with [0, 1, 2, ...] so that never at end", () => {
65
+ const bytes: number[] = []
66
+
67
+ for (let i = 0; i < 1000; i++) {
68
+ bytes.push(i % 256)
69
+ }
70
+
71
+ it("fails when reading more than 8 bits at a time", () => {
72
+ const r = Bits.makeReader(bytes)
73
+ expect(() => r.readBits(9)).toThrow()
74
+ })
75
+
76
+ it("after reading 8 bits and moving to byte boundary (which has no effect), returns 1 when reading a byte ", () => {
77
+ const r = Bits.makeReader(bytes)
78
+ r.readBits(8)
79
+ r.moveToByteBoundary()
80
+ expect(r.readByte()).toBe(1)
81
+ })
82
+
83
+ it("after reading 7 bits and moving to byte boundary, returns 1 when reading a byte", () => {
84
+ const r = Bits.makeReader(bytes)
85
+ r.readBits(7)
86
+ r.moveToByteBoundary()
87
+ expect(r.readByte()).toBe(1)
88
+ })
89
+
90
+ it("after forcing a move to byte boundary from start, returns 1 when reading a byte", () => {
91
+ const r = Bits.makeReader(bytes)
92
+ r.moveToByteBoundary(true)
93
+ expect(r.readByte()).toBe(1)
94
+ })
95
+ })
96
+ })
97
+
98
+ describe("Bits.Writer", () => {
99
+ describe("initialized without writing any bits", () => {
100
+ it("finalizes as []", () => {
101
+ expect(Bits.makeWriter().finalize(false)).toEqual([])
102
+ })
103
+
104
+ it("finalizes as [] after writing an empty bit-string", () => {
105
+ expect(Bits.makeWriter().writeBits("").finalize(false)).toEqual([])
106
+ })
107
+
108
+ it('fails when writing a bit-string not consisting of only "0"s and "1"s', () => {
109
+ const w = Bits.makeWriter()
110
+ expect(() => {
111
+ w.writeBits("2")
112
+ }).toThrow()
113
+ })
114
+
115
+ it("fails when writing -1 as a byte", () => {
116
+ const w = Bits.makeWriter()
117
+ expect(() => w.writeByte(-1)).toThrow()
118
+ })
119
+
120
+ it("fails when writing 256 as a byte", () => {
121
+ const w = Bits.makeWriter()
122
+ expect(() => w.writeByte(256)).toThrow()
123
+ })
124
+ })
125
+
126
+ describe('initialized by writing "0", "" (empty string), and then "1"', () => {
127
+ it("finalizes as [0b01000001]", () => {
128
+ expect(
129
+ Bits.makeWriter().writeBits("0").writeBits("1").finalize(false)
130
+ ).toEqual([0b01000001])
131
+ })
132
+
133
+ it("finalizes as [] after popping 2 bits", () => {
134
+ const w = Bits.makeWriter()
135
+ w.writeBits("0").writeBits("").writeBits("1").pop(2)
136
+ expect(w.finalize(false)).toEqual([])
137
+ })
138
+
139
+ it("fails when popping 3 bits", () => {
140
+ const w = Bits.makeWriter()
141
+ expect(() =>
142
+ w.writeBits("0").writeBits("").writeBits("1").pop(3)
143
+ ).toThrow()
144
+ })
145
+
146
+ it("finalizes as a bit-string with length divisible by 8", () => {
147
+ const w = Bits.makeWriter()
148
+ w.writeBits("0").writeBits("1").finalize(false)
149
+ expect(w.length % 8).toBe(0)
150
+ })
151
+ })
152
+
153
+ describe("initialized by writing 7 as a single byte", () => {
154
+ it("finalizes as [7]", () => {
155
+ const w = Bits.makeWriter()
156
+ expect(w.writeByte(7).finalize(false)).toEqual([7])
157
+ })
158
+
159
+ it("finalizes as [7, 1] if force is set to true", () => {
160
+ const w = Bits.makeWriter()
161
+ expect(w.writeByte(7).finalize(true)).toEqual([7, 1])
162
+ })
163
+
164
+ it('returns "111" when popping 3 bits', () => {
165
+ const w = Bits.makeWriter()
166
+ w.writeByte(7)
167
+ expect(w.pop(3)).toBe("111")
168
+ })
169
+
170
+ it("fails when popping a negative number of bits", () => {
171
+ const w = Bits.makeWriter()
172
+ w.writeByte(7)
173
+ expect(() => w.pop(-1)).toThrow()
174
+ })
175
+
176
+ it("returns an empty string when when popping 0 bits", () => {
177
+ const w = Bits.makeWriter()
178
+ w.writeByte(7)
179
+ expect(w.pop(0)).toBe("")
180
+ })
181
+
182
+ it("after popping 3 bits, finalizes as [1]", () => {
183
+ const w = Bits.makeWriter()
184
+ w.writeByte(7).pop(3)
185
+ expect(w.finalize(false)).toEqual([1])
186
+ })
187
+ })
188
+ })
189
+
190
+ describe("Bits.fromByte", () => {
191
+ describe("calling with 7 as the input byte", () => {
192
+ it('returns "0b00000111"', () => {
193
+ expect(Bits.fromByte(7)).toEqual(Either.right("0b00000111"))
194
+ })
195
+
196
+ it('returns "00000111" if prefix=false', () => {
197
+ expect(Bits.fromByte(7, 8, false)).toEqual(Either.right("00000111"))
198
+ })
199
+
200
+ it('returns "111" if n=3 and prefix=false', () => {
201
+ expect(Bits.fromByte(7, 3, false)).toEqual(Either.right("111"))
202
+ })
203
+
204
+ it("fails if n=2", () => {
205
+ expect(Bits.fromByte(7, 2)._tag).toBe("Left")
206
+ })
207
+ })
208
+
209
+ it("fails for 0 if n=0", () => {
210
+ expect(Bits.fromByte(0, 0, false)._tag).toBe("Left")
211
+ })
212
+
213
+ it("fails for a negative number", () => {
214
+ expect(Bits.fromByte(-1)._tag).toBe("Left")
215
+ })
216
+
217
+ it("fails for a non-whole number", () => {
218
+ expect(Bits.fromByte(3.14)._tag).toBe("Left")
219
+ })
220
+
221
+ it("fails for a number larger than 255", () => {
222
+ expect(Bits.fromByte(256)._tag).toBe("Left")
223
+ })
224
+ })
225
+
226
+ describe("Bits.getBit", () => {
227
+ it("get first bit of #00ff returns 0", () => {
228
+ expect(Bits.getBit([0x00, 0xff], 0)).toBe(0)
229
+ })
230
+
231
+ it("get last bit of #00ff returns 1", () => {
232
+ expect(Bits.getBit([0x00, 0xff], 15)).toBe(1)
233
+ })
234
+
235
+ it("returns 0 when getting indexing past bytes", () => {
236
+ expect(Bits.getBit([0x00, 0xff], 16)).toBe(0)
237
+ })
238
+
239
+ it("returns 0 when calling with negative index", () => {
240
+ expect(Bits.getBit([0x00, 0xff], -1)).toBe(0)
241
+ })
242
+ })
243
+
244
+ describe("Bits.mask", () => {
245
+ describe("calling with 0b11111111 as the input byte", () => {
246
+ it("returns 0b0111 if range=[1, 4)", () => {
247
+ expect(Bits.mask(0b11111111, 1, 4)).toBe(0b0111)
248
+ })
249
+
250
+ it("returns 0b11111111 if range=[0, 8)", () => {
251
+ const bits = 0b11111111
252
+ expect(Bits.mask(bits, 0, 8)).toBe(bits)
253
+ })
254
+
255
+ it("fails for range=[1, 1)", () => {
256
+ expect(() => Bits.mask(0b11111111, 1, 1)).toThrow()
257
+ })
258
+
259
+ it("fails for a range starting with a negative number", () => {
260
+ expect(() => Bits.mask(0b11111111, -1, 8)).toThrow()
261
+ })
262
+
263
+ it("fails for a range starting after 7", () => {
264
+ expect(() => Bits.mask(0b11111111, 8, 9)).toThrow()
265
+ })
266
+
267
+ it("fails for a range ending after 8", () => {
268
+ expect(() => Bits.mask(0b11111111, 0, 9)).toThrow()
269
+ })
270
+ })
271
+
272
+ it("fails for a negative input number", () => {
273
+ expect(() => Bits.mask(-1, 0, 8)).toThrow()
274
+ })
275
+
276
+ it("fails for an input number larger than 255", () => {
277
+ expect(() => Bits.mask(256, 0, 8)).toThrow()
278
+ })
279
+ })
280
+
281
+ describe("Bits.pad", () => {
282
+ describe('calling with "1111" as a bit-string', () => {
283
+ it('returns "00001111" if n=8', () => {
284
+ expect(Bits.pad("1111", 8)).toBe("00001111")
285
+ })
286
+
287
+ it('returns "001111" if n=3 (pads to next multiple of n if n is less than the number of bits)', () => {
288
+ expect(Bits.pad("1111", 3)).toBe("001111")
289
+ })
290
+
291
+ it('returns "1111" if n=4 (does nothing if n is equal to the input number of bits)', () => {
292
+ const bits = "1111"
293
+ expect(Bits.pad(bits, bits.length)).toBe(bits)
294
+ })
295
+
296
+ it("fails for negative n", () => {
297
+ expect(() => Bits.pad("1111", -1)).toThrow()
298
+ })
299
+ })
300
+ })
@@ -0,0 +1,398 @@
1
+ import { Either } from "effect"
2
+ import * as Bytes from "./Bytes.js"
3
+
4
+ /**
5
+ * Read non-byte aligned numbers
6
+ */
7
+ export interface Reader {
8
+ isAtEnd(): boolean
9
+ moveToByteBoundary(force?: boolean): void
10
+ readBits(n: number): number
11
+ readByte(): number
12
+ }
13
+
14
+ /**
15
+ * @param bytes
16
+ * @param truncate defaults to true
17
+ * @returns {BitReader}
18
+ */
19
+ export function makeReader(
20
+ bytes: string | number[] | Uint8Array,
21
+ truncate: boolean = true
22
+ ): Reader {
23
+ return new ReaderImpl(bytes, truncate ?? true)
24
+ }
25
+
26
+ class ReaderImpl implements Reader {
27
+ private readonly view: Uint8Array
28
+
29
+ /**
30
+ * bit position, not byte position
31
+ */
32
+ private pos: number
33
+
34
+ /**
35
+ * If true then read last bits as low part of number, if false pad with zero bits (only applies when trying to read more bits than there are left )
36
+ */
37
+ private readonly truncate: boolean
38
+
39
+ /**
40
+ * @param bytes
41
+ * @param truncate determines behavior when reading too many bits
42
+ */
43
+ constructor(bytes: string | number[] | Uint8Array, truncate: boolean = true) {
44
+ this.view = Bytes.toUint8Array(bytes)
45
+
46
+ this.pos = 0
47
+ this.truncate = truncate
48
+ }
49
+
50
+ isAtEnd(): boolean {
51
+ return Math.trunc(this.pos / 8) >= this.view.length
52
+ }
53
+
54
+ /**
55
+ * Moves position to next byte boundary
56
+ * @param force
57
+ * If true then move to next byte boundary if already at byte boundary
58
+ */
59
+ moveToByteBoundary(force: boolean = false): void {
60
+ if (this.pos % 8 != 0) {
61
+ const n = 8 - (this.pos % 8)
62
+
63
+ void this.readBits(n)
64
+ } else if (force) {
65
+ this.readBits(8)
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Reads a number of bits (<= 8) and returns the result as an unsigned number
71
+ * @param n number of bits to read
72
+ * @returns
73
+ * @throws
74
+ * If at end
75
+ * @throws
76
+ * If n is larger than 8
77
+ */
78
+ readBits(n: number): number {
79
+ if (n > 8) {
80
+ throw new RangeError(
81
+ `Reading more than 1 byte (trying to read ${n} bits)`
82
+ )
83
+ }
84
+
85
+ let leftShift = 0
86
+ if (this.pos + n > this.view.length * 8) {
87
+ const newN = this.view.length * 8 - this.pos
88
+
89
+ if (!this.truncate) {
90
+ leftShift = n - newN
91
+ }
92
+
93
+ n = newN
94
+ }
95
+
96
+ if (n == 0) {
97
+ throw new Error("Bits.Reader is at end")
98
+ }
99
+
100
+ // it is assumed we don't need to be at the byte boundary
101
+
102
+ let res = 0
103
+ let i0 = this.pos
104
+
105
+ for (let i = this.pos + 1; i <= this.pos + n; i++) {
106
+ if (i % 8 == 0) {
107
+ const nPart = i - i0
108
+
109
+ res += mask(this.view[Math.trunc(i / 8) - 1], i0 % 8, 8) << (n - nPart)
110
+
111
+ i0 = i
112
+ } else if (i == this.pos + n) {
113
+ res += mask(this.view[Math.trunc(i / 8)], i0 % 8, i % 8)
114
+ }
115
+ }
116
+
117
+ this.pos += n
118
+ return res << leftShift
119
+ }
120
+
121
+ /**
122
+ * Reads 8 bits
123
+ * @returns
124
+ */
125
+ readByte(): number {
126
+ return this.readBits(8)
127
+ }
128
+ }
129
+
130
+ /**
131
+ * BitWriter turns a string of '0's and '1's into a list of bytes.
132
+ * Finalization pads the bits using '0*1' if not yet aligned with the byte boundary.
133
+ */
134
+ export interface Writer {
135
+ readonly length: number
136
+ finalize(force?: boolean): number[]
137
+ padToByteBoundary(force?: boolean): void
138
+ pop(n: number): string
139
+ writeBits(bitChars: string): Writer
140
+ writeByte(byte: number): Writer
141
+ }
142
+
143
+ /**
144
+ * @returns
145
+ * Writer instance
146
+ */
147
+ export function makeWriter() {
148
+ return new WriterImpl()
149
+ }
150
+
151
+ class WriterImpl implements Writer {
152
+ /**
153
+ * Concatenated and padded upon finalization
154
+ */
155
+ private readonly parts: string[]
156
+
157
+ /**
158
+ * Number of bits written so far
159
+ */
160
+ private n: number
161
+
162
+ constructor() {
163
+ this.parts = []
164
+ this.n = 0
165
+ }
166
+
167
+ get length(): number {
168
+ return this.n
169
+ }
170
+
171
+ /**
172
+ * Pads the Bits.Writer to align with the byte boundary and returns the resulting bytes.
173
+ * @param force force padding (will add one byte if already aligned)
174
+ * @returns
175
+ */
176
+ finalize(force: boolean = true): number[] {
177
+ this.padToByteBoundary(force)
178
+
179
+ const chars = this.parts.join("")
180
+
181
+ const bytes = []
182
+
183
+ for (let i = 0; i < chars.length; i += 8) {
184
+ const byteChars = chars.slice(i, i + 8)
185
+ const byte = parseInt(byteChars, 2)
186
+
187
+ bytes.push(byte)
188
+ }
189
+
190
+ return bytes
191
+ }
192
+
193
+ /**
194
+ * Add padding to the BitWriter in order to align with the byte boundary.
195
+ * @param force
196
+ * If 'force == true' then 8 bits are added if the Writer is already aligned.
197
+ */
198
+ padToByteBoundary(force: boolean = false): void {
199
+ let nPad = 0
200
+ if (this.n % 8 != 0) {
201
+ nPad = 8 - (this.n % 8)
202
+ } else if (force) {
203
+ nPad = 8
204
+ }
205
+
206
+ if (nPad != 0) {
207
+ const padding = new Array(nPad).fill("0")
208
+ padding[nPad - 1] = "1"
209
+
210
+ this.parts.push(padding.join(""))
211
+
212
+ this.n += nPad
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Pop n bits of the end
218
+ * @param n
219
+ * @returns
220
+ */
221
+ pop(n: number): string {
222
+ if (n > this.n) {
223
+ throw new Error(
224
+ `Too many bits to pop, only have ${this.n} bits, but want n=${n}`
225
+ )
226
+ }
227
+
228
+ const n0 = n
229
+
230
+ const parts: string[] = []
231
+
232
+ while (n > 0) {
233
+ const last = this.parts.pop()
234
+
235
+ if (last !== undefined) {
236
+ if (last.length <= n) {
237
+ parts.unshift(last)
238
+ n -= last.length
239
+ } else {
240
+ parts.unshift(last.slice(last.length - n))
241
+ this.parts.push(last.slice(0, last.length - n))
242
+ n = 0
243
+ }
244
+ }
245
+ }
246
+
247
+ this.n -= n0
248
+
249
+ const bits = parts.join("")
250
+
251
+ if (bits.length != n0) {
252
+ throw new Error(
253
+ `Internal error: expected ${n0} bits popped, but popped ${bits.length}`
254
+ )
255
+ }
256
+
257
+ return bits
258
+ }
259
+
260
+ /**
261
+ * Write a string of '0's and '1's to the BitWriter.
262
+ * Returns the BitWriter to enable chaining
263
+ * @param bitChars
264
+ * @returns
265
+ * Self so these calls can be chain
266
+ */
267
+ writeBits(bitChars: string): Writer {
268
+ for (const c of bitChars) {
269
+ if (c != "0" && c != "1") {
270
+ throw new Error(`Bit string contains invalid chars: ${bitChars}`)
271
+ }
272
+ }
273
+
274
+ this.parts.push(bitChars)
275
+ this.n += bitChars.length
276
+
277
+ return this
278
+ }
279
+
280
+ /**
281
+ * Returns the BitWriter to enable chaining
282
+ * @param byte
283
+ * @returns
284
+ * Self so these calls can be chain
285
+ */
286
+ writeByte(byte: number): Writer {
287
+ if (byte < 0 || byte > 255) {
288
+ throw new Error(`Invalid byte: ${byte}`)
289
+ }
290
+
291
+ this.writeBits(pad(byte.toString(2), 8))
292
+
293
+ return this
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Converts a 8 bit integer number into a bit string with an optional "0b" prefix.
299
+ * The result is padded with leading zeroes to become 'n' chars long ('2 + n' chars long if you count the "0b" prefix).
300
+ * @example
301
+ * byteToBits(7) == "0b00000111"
302
+ * @param {number} b
303
+ * @param {number} n
304
+ * @param {boolean} prefix
305
+ * @returns {string}
306
+ */
307
+ export function fromByte(
308
+ b: number,
309
+ n: number = 8,
310
+ prefix: boolean = true
311
+ ): Either.Either<string, RangeError> {
312
+ if (b < 0 || b > 255) {
313
+ return Either.left(new RangeError(`Invalid byte: ${b}`))
314
+ }
315
+
316
+ const bits = b.toString(2)
317
+
318
+ if (n < bits.length) {
319
+ return Either.left(
320
+ new RangeError(
321
+ `n is smaller than the number of bits: ${n} < ${bits.length}`
322
+ )
323
+ )
324
+ }
325
+
326
+ const s = pad(bits, n)
327
+
328
+ if (prefix) {
329
+ return Either.right("0b" + s)
330
+ } else {
331
+ return Either.right(s)
332
+ }
333
+ }
334
+
335
+ /**
336
+ * @param bytes
337
+ * @param i
338
+ * bit index
339
+ * @returns
340
+ * 0 or 1
341
+ */
342
+ export function getBit(bytes: number[], i: number): 0 | 1 {
343
+ return ((bytes[Math.floor(i / 8)] >> (i % 8)) & 1) as 0 | 1
344
+ }
345
+
346
+ const BIT_MASKS = [
347
+ 0b11111111, 0b01111111, 0b00111111, 0b00011111, 0b00001111, 0b00000111,
348
+ 0b00000011, 0b00000001
349
+ ]
350
+
351
+ /**
352
+ * Masks bits of `b` by setting bits outside the range `[i0, i1)` to 0.
353
+ * `b` is an 8 bit integer (i.e. number between 0 and 255).
354
+ * The return value is also an 8 bit integer, shifted right by `i1`.
355
+ * @example
356
+ * maskBits(0b11111111, 1, 4) == 0b0111 // (i.e. 7)
357
+ * @param b
358
+ * @param i0
359
+ * @param i1
360
+ * @returns
361
+ */
362
+ export function mask(b: number, i0: number, i1: number): number {
363
+ if (i0 >= i1 || i0 < 0 || i0 > 7 || i1 > 8 || b < 0 || b > 255) {
364
+ throw new RangeError(
365
+ `Invalid Bits.mask arguments: b=${b}, i0=${i0}, i1=${i1}`
366
+ )
367
+ }
368
+
369
+ return (b & BIT_MASKS[i0]) >> (8 - i1)
370
+ }
371
+
372
+ /**
373
+ * Prepends zeroes to a bit-string so that 'result.length == n'.
374
+ * If `n < nCurrent`, pad to next multiple of `n`.
375
+ * @example
376
+ * padBits("1111", 8) == "00001111"
377
+ * @param bits
378
+ * @param n
379
+ * @returns
380
+ * @throws
381
+ * If n is zero or negative
382
+ */
383
+ export function pad(bits: string, n: number): string {
384
+ const nBits = bits.length
385
+
386
+ if (nBits == n) {
387
+ return bits
388
+ } else if (n <= 0) {
389
+ throw new RangeError(`Expected pad length n to be > 0, got n=${n}`)
390
+ } else if (nBits % n != 0) {
391
+ // padded to multiple of n
392
+ const nPad = n - (nBits % n)
393
+
394
+ bits = new Array(nPad).fill("0").join("") + bits
395
+ }
396
+
397
+ return bits
398
+ }