functionalscript 0.0.303 → 0.0.307

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.
@@ -21,18 +21,18 @@ const split = path => path.split('/')
21
21
  /** @typedef {readonly[list.List<string>] | undefined} OptionList */
22
22
 
23
23
  /** @type {(s: OptionList) => (items: string) => OptionList} */
24
- const normItemsOp = prior => item => {
24
+ const normItemsOp = prior => first => {
25
25
  if (prior === undefined) { return undefined }
26
- const priorList = prior[0]
27
- switch (item) {
26
+ const tail = prior[0]
27
+ switch (first) {
28
28
  case '': case '.': { return prior }
29
29
  case '..': {
30
- const result = list.next(priorList)
30
+ const result = list.next(tail)
31
31
  if (result === undefined) { return undefined }
32
32
  return [result.tail]
33
33
  }
34
34
  default: {
35
- return [list.nonEmpty(item)(priorList)]
35
+ return [{ first, tail }]
36
36
  }
37
37
  }
38
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.0.303",
3
+ "version": "0.0.307",
4
4
  "description": "FunctionalScript is a functional subset of JavaScript",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/sha2/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ const array = require('../types/array')
2
+
1
3
  /**
2
4
  * @typedef {{
3
5
  * readonly f: (i: number) => number
@@ -5,12 +7,12 @@
5
7
  * }} HashInput
6
8
  */
7
9
 
8
- /** @typedef {Int32Array} Hash8 */
10
+ /** @typedef {array.Array8<number>} Hash8 */
9
11
 
10
- /** @typedef {Int32Array} Array16 */
12
+ /** @typedef {array.Array16<number>} Array16 */
11
13
 
12
14
  /** @type {(input: number) => (pos: number) => number} */
13
- const appendOne = input => pos => input | (1 << 31 - pos)
15
+ const appendOneWithZeros = input => pos => (input >> pos << pos) | (1 << pos)
14
16
 
15
17
  /** @type {(input: number) => (pos: number) => number} */
16
18
  const mod = a => b => (a % b + b) % b
@@ -24,7 +26,7 @@ const padding = input => bitsCount => {
24
26
  i < appendBlockIndex ?
25
27
  input[i] :
26
28
  i === appendBlockIndex ?
27
- (appendBlockIndex >= input.length ? 0x8000_0000 : appendOne(input[appendBlockIndex])(bitsCount % 32)) :
29
+ (appendBlockIndex >= input.length ? 0x8000_0000 : appendOneWithZeros(input[appendBlockIndex])(31 - bitsCount % 32)) :
28
30
  i === length - 2 ?
29
31
  (bitsCount / 0x1_0000_0000) | 0 :
30
32
  i === length - 1 ?
@@ -72,46 +74,59 @@ const smallSigma0 = smallSigma(7)(18)(3)
72
74
  const smallSigma1 = smallSigma(17)(19)(10)
73
75
 
74
76
  /** @type {Hash8} */
75
- const init256 = new Int32Array([0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19])
77
+ const init256 = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]
76
78
 
77
79
  /** @type {(input: readonly number[]) => (bitsCount: number) => Hash8} */
78
80
  const computeSha256 = input => bitsCount => compute(input)(bitsCount)(init256)
79
81
 
80
82
  /** @type {Hash8} */
81
- const init224 = new Int32Array([0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4])
83
+ const init224 = [0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4]
82
84
 
83
85
  const k = [
84
- new Int32Array([
86
+ [
85
87
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
86
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174]),
87
- new Int32Array([
88
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174],
89
+ [
88
90
  0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
89
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967]),
90
- new Int32Array([
91
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967],
92
+ [
91
93
  0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
92
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070]),
93
- new Int32Array([
94
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070],
95
+ [
94
96
  0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
95
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2]),
97
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2],
96
98
  ];
97
99
 
98
100
  /** @type {(input: readonly number[]) => (bitsCount: number) => Hash8} */
99
101
  const computeSha224 = input => bitsCount => compute(input)(bitsCount)(init224)
100
102
 
103
+ /** @type {(a: array.Array4<number>) => number} */
104
+ const wi = a => (smallSigma1(a[0]) + a[1] + smallSigma0(a[2]) + a[3]) | 0
105
+
101
106
  /** @type {(input: Array16) => Array16} */
102
107
  const nextW = w => {
103
- for (let t = 0; t < 16; t++) {
104
- w[t] = smallSigma1(w[(t + 14) & 0xF]) + w[(t + 9) & 0xF] + smallSigma0(w[(t + 1) & 0xF]) + w[t]
105
- }
106
- return w
108
+ const _0 = wi([w[14], w[ 9], w[ 1], w[ 0]])
109
+ const _1 = wi([w[15], w[10], w[ 2], w[ 1]])
110
+ const _2 = wi([ _0, w[11], w[ 3], w[ 2]])
111
+ const _3 = wi([ _1, w[12], w[ 4], w[ 3]])
112
+ const _4 = wi([ _2, w[13], w[ 5], w[ 4]])
113
+ const _5 = wi([ _3, w[14], w[ 6], w[ 5]])
114
+ const _6 = wi([ _4, w[15], w[ 7], w[ 6]])
115
+ const _7 = wi([ _5, _0, w[ 8], w[ 7]])
116
+ const _8 = wi([ _6, _1, w[ 9], w[ 8]])
117
+ const _9 = wi([ _7, _2, w[10], w[ 9]])
118
+ const _A = wi([ _8, _3, w[11], w[10]])
119
+ const _B = wi([ _9, _4, w[12], w[11]])
120
+ const _C = wi([ _A, _5, w[13], w[12]])
121
+ const _D = wi([ _B, _6, w[14], w[13]])
122
+ const _E = wi([ _C, _7, w[15], w[14]])
123
+ const _F = wi([ _D, _8, _0, w[15]])
124
+ return [_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _A, _B, _C, _D, _E, _F]
107
125
  }
108
126
 
109
127
  /** @type {(init: Hash8) => (data: Array16) => Hash8} */
110
128
  const compress = init => data => {
111
- let w = new Int32Array(16)
112
- for (let t = 0; t < 16; t++) {
113
- w[t] = data[t]
114
- }
129
+ let w = data
115
130
 
116
131
  let a = init[0]
117
132
  let b = init[1]
@@ -139,16 +154,16 @@ const compress = init => data => {
139
154
  w = nextW(w)
140
155
  }
141
156
 
142
- return new Int32Array([
143
- init[0] + a,
144
- init[1] + b,
145
- init[2] + c,
146
- init[3] + d,
147
- init[4] + e,
148
- init[5] + f,
149
- init[6] + g,
150
- init[7] + h,
151
- ])
157
+ return [
158
+ (init[0] + a) | 0,
159
+ (init[1] + b) | 0,
160
+ (init[2] + c) | 0,
161
+ (init[3] + d) | 0,
162
+ (init[4] + e) | 0,
163
+ (init[5] + f) | 0,
164
+ (init[6] + g) | 0,
165
+ (init[7] + h) | 0,
166
+ ]
152
167
  }
153
168
 
154
169
  /** @type {(input: readonly number[]) => (bitsCount: number) => (init: Hash8) => Hash8} */
@@ -160,9 +175,9 @@ const compute = input => bitsCount => init => {
160
175
  const chunkCount = length / 16
161
176
  for (let i = 0; i < chunkCount; i++) {
162
177
  const s = i * 16
163
- result = compress(result)(new Int32Array([
178
+ result = compress(result)([
164
179
  f(s + 0), f(s + 1), f(s + 2), f(s + 3), f(s + 4), f(s + 5), f(s + 6), f(s + 7),
165
- f(s + 8), f(s + 9), f(s + 10), f(s + 11), f(s + 12), f(s + 13), f(s + 14), f(s + 15)]))
180
+ f(s + 8), f(s + 9), f(s + 10), f(s + 11), f(s + 12), f(s + 13), f(s + 14), f(s + 15)])
166
181
  }
167
182
 
168
183
  return result
package/sha2/test.js CHANGED
@@ -34,20 +34,27 @@ const stringify = a => json.stringify(sort)(a)
34
34
 
35
35
  {
36
36
  const hash = _.computeSha256([])(0)
37
- const result = stringify(Array.from(hash).map(toHexString))
37
+ const result = stringify(hash.map(toHexString))
38
38
  if (result !== '["e3b0c442","98fc1c14","9afbf4c8","996fb924","27ae41e4","649b934c","a495991b","7852b855"]') { throw result }
39
39
  }
40
40
 
41
41
  {
42
42
  const hash = _.computeSha224([])(0)
43
- const result = stringify(Array.from(hash).map(toHexString))
43
+ const result = stringify(hash.map(toHexString))
44
44
  if (result !== '["d14a028c","2a3a2bc9","476102bb","288234c4","15a2b01f","828ea62a","c5b3e42f","bdd387cb"]') { throw result }
45
45
  }
46
46
 
47
47
  {
48
48
  //[0x68656C6C, 0x6F20776F, 0x726C6400] represents phrase 'hello world'
49
49
  const hash = _.computeSha256([0x68656C6C, 0x6F20776F, 0x726C6400])(88)
50
- const result = stringify(Array.from(hash).map(toHexString))
50
+ const result = stringify(hash.map(toHexString))
51
+ if (result !== '["b94d27b9","934d3e08","a52e52d7","da7dabfa","c484efe3","7a5380ee","9088f7ac","e2efcde9"]') { throw result }
52
+ }
53
+
54
+ {
55
+ //[0x68656C6C, 0x6F20776F, 0x726C6488] represents phrase 'hello world' with 1's at the end
56
+ const hash = _.computeSha256([0x68656C6C, 0x6F20776F, 0x726C64FF])(88)
57
+ const result = stringify(hash.map(toHexString))
51
58
  if (result !== '["b94d27b9","934d3e08","a52e52d7","da7dabfa","c484efe3","7a5380ee","9088f7ac","e2efcde9"]') { throw result }
52
59
  }
53
60
 
@@ -60,7 +67,7 @@ const stringify = a => json.stringify(sort)(a)
60
67
  {
61
68
  const input = Array(16).fill(0x31313131)
62
69
  const hash = _.computeSha256(input)(512)
63
- const result = stringify(Array.from(hash).map(toHexString))
70
+ const result = stringify(hash.map(toHexString))
64
71
  if (result !== '["3138bb9b","c78df27c","473ecfd1","410f7bd4","5ebac1f5","9cf3ff9c","fe4db77a","ab7aedd3"]') { throw result }
65
72
  }
66
73
 
@@ -57,6 +57,8 @@ const seq = require('../list')
57
57
  * @typedef {readonly[T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T]} Array16
58
58
  */
59
59
 
60
+ /** @typedef {0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15} Index16 */
61
+
60
62
  /**
61
63
  * @template T
62
64
  * @typedef {Array1<T>| Array2<T> | Array3<T> | Array4<T> | Array5<T>} Array1_5
@@ -13,6 +13,9 @@
13
13
  /** @type {(separator: string) => Fold<string>} */
14
14
  const join = separator => prior => value => `${prior}${separator}${value}`
15
15
 
16
+ /** @type {Fold<string>} */
17
+ const concat = a => b => `${a}${b}`
18
+
16
19
  /** @type {Fold<number>} */
17
20
  const addition = a => b => a + b
18
21
 
@@ -26,8 +29,8 @@ const addition = a => b => a + b
26
29
  const logicalNot = v => !v
27
30
 
28
31
  /**
29
- * @template T
30
- * @typedef {Binary<T, T, boolean>} Equal
32
+ * @template T
33
+ * @typedef {Binary<T, T, boolean>} Equal
31
34
  */
32
35
 
33
36
  /** @type {<T>(a: T) => (b: T) => boolean} */
@@ -93,4 +96,6 @@ module.exports = {
93
96
  foldToScan,
94
97
  /** @readonly */
95
98
  counter,
99
+ /** @readonly */
100
+ concat,
96
101
  }
@@ -44,14 +44,13 @@ const { logicalNot, strictEqual, stateScanToScan, reduceToScan, foldToScan } = r
44
44
  * }} Concat
45
45
  */
46
46
 
47
- /** @type {<T>(first: T) => (tail: List<T>) => NonEmpty<T>} */
48
- const nonEmpty = first => tail => ({ first, tail })
49
-
50
- /** @type {(i: number) => <T>(array: readonly T[]) => Result<T>} */
51
- const fromArrayAt = i => array => i < array.length ? nonEmpty(array[i])(() => fromArrayAt(i + 1)(array)) : undefined
52
-
53
47
  /** @type {<T>(array: readonly T[]) => Result<T>} */
54
- const fromArray = fromArrayAt(0)
48
+ const fromArray = array => {
49
+ /** @typedef {typeof array extends readonly (infer T)[] ? T : never} T */
50
+ /** @type {(i: number) => Result<T>} */
51
+ const at = i => i < array.length ? { first: array[i], tail: () => at(i + 1) } : undefined
52
+ return at(0)
53
+ }
55
54
 
56
55
  /** @type {<T>(a: List<T>) => (b: List<T>) => List<T>} */
57
56
  const concat = a => b => b === undefined ? a : ({ isConcat: true, a, b })
@@ -78,7 +77,7 @@ const next = list => {
78
77
  }
79
78
 
80
79
  if (a !== undefined) {
81
- return nonEmpty(a.first)(concat(a.tail)(b))
80
+ return { first: a.first, tail: concat(a.tail)(b) }
82
81
  }
83
82
 
84
83
  if (b === undefined) { return undefined }
@@ -120,7 +119,7 @@ const flatStep = n => concat(n.first)(flat(n.tail))
120
119
  const flat = apply(flatStep)
121
120
 
122
121
  /** @type {<I, O>(f: (value: I) => O) => (n: NonEmpty<I>) => List<O>} */
123
- const mapStep = f => n => nonEmpty(f(n.first))(map(f)(n.tail))
122
+ const mapStep = f => n => ({ first: f(n.first), tail: map(f)(n.tail) })
124
123
 
125
124
  /** @type {<I, O>(f: (value: I) => O) => (input: List<I>) => List<O>} */
126
125
  const map = f => apply(mapStep(f))
@@ -131,7 +130,7 @@ const flatMap = f => compose(map(f))(flat)
131
130
  /** @type {<T>(f: (value: T) => boolean) => (n: NonEmpty<T>) => List<T>} */
132
131
  const filterStep = f => n => {
133
132
  const tail = filter(f)(n.tail)
134
- return f(n.first) ? nonEmpty(n.first)(tail) : tail
133
+ return f(n.first) ? { first: n.first, tail } : tail
135
134
  }
136
135
 
137
136
  /** @type {<T>(f: (value: T) => boolean) => (input: List<T>) => List<T>} */
@@ -140,20 +139,20 @@ const filter = f => apply(filterStep(f))
140
139
  /** @type {<I, O>(f: (value: I) => O|undefined) => (n: NonEmpty<I>) => List<O>} */
141
140
  const filterMapStep = f => n => {
142
141
  const [first, tail] = [f(n.first), filterMap(f)(n.tail)]
143
- return first === undefined ? tail : nonEmpty(first)(tail)
142
+ return first === undefined ? tail : { first, tail }
144
143
  }
145
144
 
146
145
  /** @type {<I, O>(f: (value: I) => O|undefined) => (input: List<I>) => List<O>} */
147
146
  const filterMap = f => apply(filterMapStep(f))
148
147
 
149
148
  /** @type {<T>(f: (value: T) => boolean) => (n: NonEmpty<T>) => List<T>} */
150
- const takeWhileStep = f => n => f(n.first) ? nonEmpty(n.first)(takeWhile(f)(n.tail)) : undefined
149
+ const takeWhileStep = f => n => f(n.first) ? { first: n.first, tail: takeWhile(f)(n.tail) } : undefined
151
150
 
152
151
  /** @type {<T>(f: (value: T) => boolean) => (input: List<T>) => List<T>} */
153
152
  const takeWhile = f => apply(takeWhileStep(f))
154
153
 
155
154
  /** @type {(n: number) => <T>(result: NonEmpty<T>) => List<T>} */
156
- const takeStep = n => ne => 0 < n ? nonEmpty(ne.first)(take(n - 1)(ne.tail)) : undefined
155
+ const takeStep = n => ne => 0 < n ? { first: ne.first, tail: take(n - 1)(ne.tail) } : undefined
157
156
 
158
157
  /** @type {(n: number) => <T>(input: List<T>) => List<T>} */
159
158
  const take = n => apply(takeStep(n))
@@ -177,10 +176,11 @@ const first = def => input => {
177
176
  return result.first
178
177
  }
179
178
 
180
- /** @type {<D>(def: D) => <T>(input: List<T>) => D|T} */
181
- const last = def => input => {
182
- /** @typedef {typeof input extends List<infer T> ? T : never} T */
183
- let i = nonEmpty(/** @type {(typeof def)|T} */(def))(input)
179
+ /** @type {<D>(first: D) => <T>(tail: List<T>) => D|T} */
180
+ const last = first => tail => {
181
+ /** @typedef {typeof tail extends List<infer T> ? T : never} T */
182
+ /** @type {NonEmpty<typeof first|T>} */
183
+ let i = { first, tail }
184
184
  while (true) {
185
185
  const result = next(i.tail)
186
186
  if (result === undefined) {
@@ -212,19 +212,19 @@ const includes = value => input => some(map(strictEqual(value))(input))
212
212
  const countdown = count => () => {
213
213
  if (count <= 0) { return undefined }
214
214
  const first = count - 1
215
- return nonEmpty(first)(countdown(first))
215
+ return { first, tail: countdown(first) }
216
216
  }
217
217
 
218
218
  /** @type {<T>(list: List<T>) => List<T>} */
219
219
  const cycle = list => () => {
220
220
  const i = next(list)
221
- return i === undefined ? undefined : nonEmpty(i.first)(concat(i.tail)(cycle(list)))
221
+ return i === undefined ? undefined : { first: i.first, tail: concat(i.tail)(cycle(list)) }
222
222
  }
223
223
 
224
224
  /** @type {<I, O>(op: operator.Scan<I, O>) => (ne: NonEmpty<I>) => List<O>} */
225
225
  const scanStep = op => ne => {
226
- const [o, newOp] = op(ne.first)
227
- return nonEmpty(o)(scan(newOp)(ne.tail))
226
+ const [first, newOp] = op(ne.first)
227
+ return { first, tail: scan(newOp)(ne.tail) }
228
228
  }
229
229
 
230
230
  /** @type {<I, O>(op: operator.Scan<I, O>) => (input: List<I>) => List<O>} */
@@ -263,13 +263,13 @@ const length = reduce(operator.counter)(0)
263
263
  const entryOperator = index => value => [[index, value], index + 1]
264
264
 
265
265
  /** @type {<T>(input: List<T>) => List<Entry<T>>} */
266
- const entries = (input) => {
266
+ const entries = input => {
267
267
  /** @typedef {typeof input extends List<infer T> ? T : never} T */
268
268
  return stateScan(/** @type {operator.StateScan<T, Number, Entry<T>>} */(entryOperator))(0)(input)
269
269
  }
270
270
 
271
271
  /** @type {<T>(prior: List<T>) => (value: T) => List<T>} */
272
- const reverseOperator = prior => value => nonEmpty(value)(prior)
272
+ const reverseOperator = tail => first => ({ first, tail })
273
273
 
274
274
  /** @type {<T>(input: List<T>) => List<T>} */
275
275
  const reverse = reduce(reverseOperator)(undefined)
@@ -283,26 +283,36 @@ const zip = a => b => () => {
283
283
  if (aResult === undefined) { return undefined }
284
284
  const bResult = next(b)
285
285
  if (bResult === undefined) { return undefined }
286
- return nonEmpty(tuple2(aResult.first)(bResult.first))(zip(aResult.tail)(bResult.tail))
286
+ return { first: tuple2(aResult.first)(bResult.first), tail: zip(aResult.tail)(bResult.tail) }
287
287
  }
288
288
 
289
289
  /** @type {<T>(e: operator.Equal<T>) => (a: List<T>) => (b: List<T>) => List<boolean>} */
290
290
  const equalZip = e => a => b => () => {
291
291
  const [aResult, bResult] = [next(a), next(b)]
292
292
  return aResult === undefined || bResult === undefined
293
- ? nonEmpty(aResult === bResult)(undefined)
294
- : nonEmpty(e(aResult.first)(bResult.first))(equalZip(e)(aResult.tail)(bResult.tail))
293
+ ? { first: aResult === bResult, tail: undefined }
294
+ : { first: e(aResult.first)(bResult.first), tail: equalZip(e)(aResult.tail)(bResult.tail) }
295
295
  }
296
296
 
297
297
  /** @type {<T>(e: operator.Equal<T>) => (a: List<T>) => (b: List<T>) => boolean} */
298
298
  const equal = e => a => b => every(equalZip(e)(a)(b))
299
299
 
300
+ /** @type {(s: string) => List<number>} */
301
+ const toCharCodes = s => {
302
+ /** @type {(i: number) => Result<number>} */
303
+ const at = i => {
304
+ const first = s.charCodeAt(i)
305
+ return isNaN(first) ? undefined : { first, tail: () => at(i + 1) }
306
+ }
307
+ return at(0)
308
+ }
309
+
310
+ const fromCharCodes = compose(map(String.fromCharCode))(fold(operator.concat)(''))
311
+
300
312
  module.exports = {
301
313
  /** @readonly */
302
314
  empty: undefined,
303
315
  /** @readonly */
304
- nonEmpty,
305
- /** @readonly */
306
316
  concat,
307
317
  /** @readonly */
308
318
  next,
@@ -374,4 +384,8 @@ module.exports = {
374
384
  zip,
375
385
  /** @readonly */
376
386
  equal,
387
+ /** @readonly */
388
+ toCharCodes,
389
+ /** @readonly */
390
+ fromCharCodes,
377
391
  }
@@ -234,6 +234,12 @@ const stringify = sequence => json.stringify(sort)(_.toArray(sequence))
234
234
  if (result !== false) { throw result }
235
235
  }
236
236
 
237
+ {
238
+ const r = _.toCharCodes("Hello world!")
239
+ const x = _.fromCharCodes(r)
240
+ if (x !== "Hello world!") { throw x }
241
+ }
242
+
237
243
  // stress tests
238
244
 
239
245
  const stress = () => {