0 | ||| Primitive Javascript arrays. This offers an alternative
  1 | ||| to the functionality provided by `Data.IOArray.Prims` from base.
  2 | |||
  3 | ||| In addition to mutable arrays, we also provide linear arrays
  4 | ||| for imperatively filling arrays in a pure envirionment,
  5 | ||| as well as immutable arrays, the latter being guaranteed to always
  6 | ||| return a value if an index is within bounds.
  7 | |||
  8 | ||| We use `Bits32` for indexing, as this is the type reflecting
  9 | ||| most exactly the kinds of indices use to access array elements.
 10 | ||| However, right now, `Bits32` is represented by `BigInt` at the Javascript
 11 | ||| backend. This might negatively affect performance, as we always have
 12 | ||| to convert to `Number` first (that's `Double` in Idris2).
 13 | ||| Hopefully, the backend representation will change in the future, and
 14 | ||| this will be no longer an issue.
 15 | |||
 16 | ||| In addition, `Bits32` does not offer a lot of functionality when
 17 | ||| it comes to available proofs for it being in bounds. This
 18 | ||| module provides a minimal amount of utilities for this, but again,
 19 | ||| these might hopefully go elswhere, where the whole topic is treated
 20 | ||| more thoroughly.
 21 | module JS.Array
 22 |
 23 | import Control.Monad.Either
 24 | import Decidable.Equality
 25 | import Data.DPair
 26 | import Data.List
 27 |
 28 | import JS.Any
 29 | import JS.Marshall
 30 | import JS.Undefined
 31 | import JS.Util
 32 | import JS.Buffer
 33 |
 34 | %default total
 35 |
 36 | --------------------------------------------------------------------------------
 37 | --          Utilities
 38 | --------------------------------------------------------------------------------
 39 |
 40 | ||| Type for indexing into an array of known size.
 41 | public export
 42 | Ix : Bits32 -> Type
 43 | Ix n = Subset Bits32 $ \ix => ix < n = True
 44 |
 45 | ||| Calculates the length of a list as an array index.
 46 | public export
 47 | len32 : List a -> Bits32
 48 | len32 = fromInteger . natToInteger . length
 49 |
 50 | ||| Zips a list of elements with the corresponding
 51 | ||| array indices.
 52 | public export
 53 | zipWithIndex : (as : List a) -> List (Ix $ len32 as, a)
 54 | zipWithIndex as = run [] 0 as
 55 |   -- being pragmatic here and going via `believe_me`
 56 |   where
 57 |     run : List (Ix $ len32 as, a) -> Bits32 -> List a -> List (Ix $ len32 as, a)
 58 |     run acc _ []        = reverse acc
 59 |     run acc n (x :: xs) =
 60 |       run ((Element n $ believe_me (Refl {x}), x) :: acc) (n+1) xs
 61 |
 62 | ||| Tries to convert a number into an index for
 63 | ||| an array of the given size.
 64 | public export
 65 | toIx : (size : Bits32) -> Bits32 -> Maybe (Ix size)
 66 | toIx size x = case decEq (x < size) True of
 67 |   Yes prf => Just $ Element x prf
 68 |   No  _   => Nothing
 69 |
 70 | --------------------------------------------------------------------------------
 71 | --          FFI
 72 | --------------------------------------------------------------------------------
 73 |
 74 | export
 75 | data Array : Type -> Type where [external]
 76 |
 77 | -- Note: in the backend, type parameters are passed
 78 | -- as additional erased arguments of type `undefined`
 79 | -- to the lambdas. The lambdas therefore need to take
 80 | -- additional arguments, based on the number of type parameters.
 81 |
 82 |
 83 | -- determines the size of a (potentially) mutable Array-like object.
 84 | %foreign "javascript:lambda:(u,x) => x.length"
 85 | prim__sizeIO : forall arr . arr -> PrimIO Bits32
 86 |
 87 | -- reads a value at the given index from a (potentially) mutable Array-like object.
 88 | %foreign "javascript:lambda:(u,v,arr,n) => arr[n]"
 89 | prim__readIO : forall a,arr . arr -> Bits32 -> PrimIO (UndefOr a)
 90 |
 91 | -- determines the size of an immutable Array-like object.
 92 | %foreign "javascript:lambda:(u,x) => x.length"
 93 | prim__size : forall arr . arr -> Bits32
 94 |
 95 | -- reads a value at the given index from an immutable Array-like object.
 96 | %foreign "javascript:lambda:(u,v,arr,n) => arr[n]"
 97 | prim__read : forall a,arr . arr -> Bits32 -> a
 98 |
 99 | -- writes a value to a mutable array
100 | %foreign "javascript:lambda:(u,arr,n,v) => { arr[n] = v }"
101 | prim__writeIO : forall a . Array a -> Bits32 -> a -> PrimIO ()
102 |
103 | -- creates a new mutable array of the given size
104 | %foreign "javascript:lambda:(u,n) => { return new Array(n) }"
105 | prim__newArrayIO : forall a . Bits32 -> PrimIO (Array a)
106 |
107 | -- determines, whether the given pointer is an array.
108 | %foreign "javascript:lambda:x => Array.isArray(x)?1:0"
109 | prim__isArray : AnyPtr -> Bool
110 |
111 | -- clones an Array-like object, thus creating a new array.
112 | %foreign "javascript:lambda:(_arr,_a,x) => Array.from(x)"
113 | prim__fromArrayLikeIO : forall a,arr . arr -> PrimIO (Array a)
114 |
115 | --------------------------------------------------------------------------------
116 | --          ArrayLike
117 | --------------------------------------------------------------------------------
118 |
119 | ||| Witness, that a given value represents an Array-like object.
120 | ||| Array-like objects must provide the following functionality in
121 | ||| the Javascript backend:
122 | |||
123 | |||  * a `length` property, returning the value's length as an unsigned 32bit
124 | |||    integer
125 | |||  * the ability to access values of the given `elem` type`
126 | |||    at a given index by means of the following syntax: `arr[12]`.
127 | |||
128 | ||| Javascript Arrays are, of course, Array-like, as are Strings.
129 | ||| Some other Array-likes include `NodeList` in the DOM.
130 | |||
131 | ||| Note, that this is just a witnessing interface. All functionality
132 | ||| is already implemented through functions `sizeIO` and `readIO`.
133 | ||| It is possible to clone an `ArrayLike` to an actual Javascript
134 | ||| Array by invoking `Array.from(v)` at the backend. This functionality
135 | ||| is available through `arrayDataFrom` for mutable arrays and
136 | ||| `arrayFrom` for immutable arrays.
137 | public export
138 | interface ArrayLike (0 arr : Type) (0 el : Type) | arr where
139 |
140 | export
141 | sizeIO : (HasIO io, ArrayLike arr el) => arr -> io Bits32
142 | sizeIO v = fromInteger . cast <$> primIO (prim__sizeIO v)
143 |
144 | export
145 | readIO : (HasIO io, ArrayLike arr e) => arr -> Bits32 -> io (Maybe e)
146 | readIO v ix = undeforToMaybe <$> (primIO . prim__readIO v $ toFFI ix)
147 |
148 | export
149 | ArrayLike String Char where
150 |
151 | export
152 | ArrayLike (Array a) a where
153 |
154 | export ArrayLike UInt8ClampedArray Bits8 where
155 | export ArrayLike UInt8Array Bits8 where
156 | export ArrayLike UInt16Array Bits16 where
157 | export ArrayLike UInt32Array Bits32 where
158 | export ArrayLike Int8Array Int8 where
159 | export ArrayLike Int16Array Int16 where
160 | export ArrayLike Int32Array Int32 where
161 | export ArrayLike Float64Array Double where
162 |
163 | ||| Witness, that a given value represents an immutable
164 | ||| Array-like object. The same rules as for `ArrayLike`
165 | ||| hold, with the addition, that values of types implementing
166 | ||| this interface must be immutable.
167 | ||| It is then safe to provide pure versions of `readIO` and `sizeIO`.
168 | public export
169 | interface ArrayLike arr el => IArrayLike arr el | arr where
170 |
171 | export
172 | size : IArrayLike arr el => arr -> Bits32
173 | size arr = fromInteger . cast $ prim__size arr
174 |
175 | export
176 | read : IArrayLike arr el
177 |      => (elems : arr) -> (ix : Ix $ size elems) -> el
178 | read elems (Element ix _) = prim__read elems $ toFFI ix
179 |
180 | export
181 | readMaybe : IArrayLike arr el
182 |           => (elems : arr) -> Bits32 -> Maybe el
183 | readMaybe elems = map (read elems) . toIx (size elems)
184 |
185 | ||| Returns true if the given immutable arrays contain
186 | ||| the same elements in the same order.
187 | export
188 | sameElements : (IArrayLike arr el, Eq el) => arr -> arr -> Bool
189 | sameElements a1 a2 =
190 |   case decEq (size a2) (size a1) of
191 |     Yes prf => run prf 0
192 |     No _    => False
193 |
194 |   where
195 |     run : (0 _ : size a2 = size a1) -> Bits32 -> Bool
196 |     run refl ix =
197 |       case decEq (ix < size a1) True of
198 |         No  _   => True
199 |         Yes prf =>
200 |           read a1 (Element ix prf) ==
201 |             read a2 (Element ix (rewrite refl in prf)) &&
202 |           run refl (assert_smaller ix $ ix+1)
203 |
204 | export
205 | foldlArray :
206 |   IArrayLike arr el => (acc -> el -> acc) -> acc -> arr -> acc
207 | foldlArray f ini arr = run 0 ini
208 |
209 |   where
210 |     run : Bits32 -> acc -> acc
211 |     run n res = case readMaybe arr n of
212 |       Just el => run (assert_smaller n $ n+1) (f res el)
213 |       Nothing => res
214 |
215 | export
216 | foldrArray :
217 |   IArrayLike arr el => (el -> acc -> acc) -> acc -> arr -> acc
218 | foldrArray f ini arr = run 0
219 |   where
220 |     run : Bits32 -> acc
221 |     run n = case readMaybe arr n of
222 |       Just el => f el $ run (assert_smaller n $ n+1)
223 |       Nothing => ini
224 |
225 | export
226 | arrayToList : IArrayLike arr el => arr -> List el
227 | arrayToList = foldrArray (::) Nil
228 |
229 | export
230 | IArrayLike String Char where
231 |
232 | --------------------------------------------------------------------------------
233 | --          IO Arrays
234 | --------------------------------------------------------------------------------
235 |
236 | export
237 | ToFFI (Array a) (Array a) where toFFI = id
238 |
239 | export
240 | FromFFI (Array a) (Array a) where fromFFI = Just
241 |
242 | export %inline
243 | isArray : anyVal -> Bool
244 | isArray v = prim__isArray (toFFI $ MkAny v)
245 |
246 | export
247 | writeIO : HasIO io => Array a -> Bits32 -> a -> io ()
248 | writeIO arr ix v = primIO $  prim__writeIO arr (toFFI ix) v
249 |
250 | export
251 | newArrayIO : HasIO io => Bits32 -> io (Array a)
252 | newArrayIO n = primIO $  prim__newArrayIO (toFFI n)
253 |
254 | export
255 | arrayDataFrom : (HasIO io, ArrayLike arr e) => arr -> io (Array e)
256 | arrayDataFrom arr = primIO $ prim__fromArrayLikeIO arr
257 |
258 | --------------------------------------------------------------------------------
259 | --          Arrays and Lists
260 | --------------------------------------------------------------------------------
261 |
262 | export
263 | fromListIO : HasIO io => List a -> io (Array a)
264 | fromListIO as =
265 |   let len := the Bits32 (fromInteger . natToInteger $ length as)
266 |    in newArrayIO len >>= fill 0 as
267 |
268 |   where
269 |     fill : Bits32 -> List a -> Array a -> io (Array a)
270 |     fill _ []        arr = pure arr
271 |     fill n (x :: xs) arr = do
272 |       writeIO arr n x
273 |       fill (n+1) xs arr
274 |
275 | export
276 | fromFoldableIO : (HasIO io, Foldable t) => t a -> io (Array a)
277 | fromFoldableIO = fromListIO . toList
278 |
279 | export
280 | ToFFI a b => ToFFI (List a) (IO $ Array b)
281 |   where toFFI = fromListIO . map toFFI
282 |
283 | --------------------------------------------------------------------------------
284 | --          Linear Arrays
285 | --------------------------------------------------------------------------------
286 |
287 | %foreign "javascript:lambda:(u,arr,n,v) => { arr[n] = v; return arr }"
288 | prim__write : forall a . Array a -> Bits32 -> a -> Array a
289 |
290 | %foreign "javascript:lambda:(u,n,a) => { var res = new Array(n); return res.fill(a) }"
291 | prim__newArray : forall a . Bits32 -> a -> Array a
292 |
293 | %foreign "javascript:lambda:(u,n) => { return new Array(n) }"
294 | prim__undefArray : forall a . Bits32 -> Array a
295 |
296 | %foreign "javascript:lambda:u => { return = new Array(0) }"
297 | prim__emptyArray : forall a . Array a
298 |
299 | %foreign "javascript:lambda:(u,x,v) => Array.from(v)"
300 | prim__fromArray : forall arr . arr -> Array a
301 |
302 | ||| A linear wrapper around a primitive array.
303 | export
304 | record LinArray (sze : Bits32) (a : Type) where
305 |   constructor MkLinArray
306 |   array : Array a
307 |
308 | export
309 | thaw : (1 _ : LinArray sze a) -> IO (Array a)
310 | thaw (MkLinArray arr) = pure arr
311 |
312 | -- This must not leak out, as we allocate a new array of
313 | -- `undefined` values. It is used to create an array by
314 | -- iteratively filling it up.
315 | private
316 | undefArray : (sze : Bits32) -> (1 _ : (1 _ : LinArray sze a) -> b) -> b
317 | undefArray sze f = f (MkLinArray $ prim__undefArray (toFFI sze))
318 |
319 | private
320 | unsafeWrite : (1 _ : LinArray sze a) -> (n : Bits32) -> a -> LinArray sze a
321 | unsafeWrite (MkLinArray arr) n v = MkLinArray $ prim__write arr (toFFI n) v
322 |
323 | export
324 | emptyArray : (1 _ : (1 _ : LinArray 0 a) -> b) -> b
325 | emptyArray f = f (MkLinArray $ prim__emptyArray)
326 |
327 | export
328 | newArray :
329 |      (val : a)
330 |   -> (sze : Bits32)
331 |   -> (1 _ : (1 _ : LinArray sze a) -> b)
332 |   -> b
333 | newArray v sze f = f (MkLinArray $ prim__newArray (toFFI sze) v)
334 |
335 | export
336 | withArray :
337 |      {auto _ : IArrayLike arr el}
338 |   -> (v : arr)
339 |   -> (1 _ : (1 _ : LinArray (size v) el) -> a)
340 |   -> a
341 | withArray array f = f (MkLinArray $ prim__fromArray array)
342 |
343 | export
344 | write : (1 _ : LinArray sze a) -> (ix : Ix sze) -> a -> LinArray sze a
345 | write arr (Element n _) = unsafeWrite arr n
346 |
347 | export
348 | lread :
349 |      (1 _ : LinArray sze a)
350 |   -> (ix : Ix sze)
351 |   -> Res a (const $ LinArray sze a)
352 | lread (MkLinArray arr) (Element ix _) =
353 |   prim__read arr (toFFI ix) # MkLinArray arr
354 |
355 | export
356 | lsize : (1 _ : LinArray sze a) -> Res Bits32 (\s => LinArray s a)
357 | lsize (MkLinArray arr) = fromInteger (cast $ prim__size arr) # MkLinArray arr
358 |
359 | export
360 | iterate' :
361 |      (sze : Bits32)
362 |   -> (Ix sze -> a)
363 |   -> ((1 _ : LinArray sze a) -> b)
364 |   -> b
365 | iterate' sze f g = undefArray sze (run 0)
366 |
367 |   where
368 |     run : Bits32 -> (1 _ : LinArray sze a) -> b
369 |     run n arr = case toIx sze n of
370 |       Nothing => g arr
371 |       Just ix => run (assert_smaller n $ n+1) (write arr ix (f ix))
372 |
373 | export
374 | fromList' : (as : List a) -> ((1 _ : LinArray (len32 as) a) -> b) -> b
375 | fromList' as f = undefArray (len32 as) (run $ zipWithIndex as)
376 |
377 |   where
378 |     run :  List (Ix $ len32 as, a) -> (1 _ : LinArray (len32 as) a) -> b
379 |     run []            linA = f linA
380 |     run ((ix,a) :: t) linA = run t (write linA ix a)
381 |
382 | export
383 | map' :
384 |      {auto _ : IArrayLike arr a}
385 |   -> (vs : arr)
386 |   -> ((1 _ : LinArray (size vs) b) -> c)
387 |   -> (a -> b)
388 |   -> c
389 | map' vs fromArr f = iterate' (size vs) (\ix => f (read vs ix)) fromArr
390 |
391 | export
392 | totalSize :
393 |   (IArrayLike arr1 arr2, IArrayLike arr2 el) => arr1 -> Bits32
394 | totalSize = foldlArray (\n,vs => n + size vs) 0
395 |
396 | ||| Concatenates to nested layers of immutable
397 | ||| array-like objects.
398 | export
399 | join' :
400 |      {auto _ : IArrayLike arr1 arr2}
401 |   -> {auto _ : IArrayLike arr2 el}
402 |   -> (vss : arr1)
403 |   -> ((1 _ : LinArray (totalSize {arr2} vss) el) -> a)
404 |   -> a
405 | join' vss f = undefArray (totalSize {arr2} vss) (outer 0 0)
406 |
407 |    where
408 |      inner :
409 |           (n : Bits32)
410 |        -> (pos : Bits32)
411 |        -> arr2
412 |        -> (1 _ : LinArray (totalSize {arr2} vss) el)
413 |        -> LinArray (totalSize {arr2} vss) el
414 |      inner n pos as la =
415 |        case readMaybe as n of
416 |          Just v  => inner (assert_smaller n $ n+1) pos as (unsafeWrite la (pos + n) v)
417 |          Nothing => la
418 |
419 |      outer :
420 |           (n : Bits32)
421 |        -> (pos : Bits32)
422 |        -> (1 _ : LinArray (totalSize {arr2} vss) el)
423 |        -> a
424 |      outer n pos la =
425 |        case readMaybe vss n of
426 |          Just v  => outer (assert_smaller n $ n+1) (pos + size v) (inner 0 pos v la)
427 |          Nothing => f la
428 |
429 | --------------------------------------------------------------------------------
430 | --          Immutable Arrays
431 | --------------------------------------------------------------------------------
432 |
433 | ||| An immutable array from a resource that guarantees, that
434 | ||| this will not be modified any more.
435 | |||
436 | ||| Typically, these are generated by freezing a `LinArray`
437 | ||| or by cloning an `ArrayLike` value.
438 | export
439 | record IArray (a : Type) where
440 |   constructor MkIArray
441 |   array : Array a
442 |
443 | export
444 | freezeCloneIO : (HasIO io, ArrayLike arr el) => arr -> io (IArray el)
445 | freezeCloneIO xs = MkIArray <$> primIO (prim__fromArrayLikeIO xs)
446 |
447 | export
448 | freeze : forall sze,a . (1 _ : LinArray sze a) -> IArray a
449 | freeze (MkLinArray arr) = MkIArray arr
450 |
451 | export
452 | fromList : List a -> IArray a
453 | fromList as = fromList' as freeze
454 |
455 | export
456 | singleton : a -> IArray a
457 | singleton = fromList . pure
458 |
459 | export
460 | ArrayLike (IArray a) a where
461 |
462 | export
463 | IArrayLike (IArray a) a where
464 |
465 | export
466 | concat : IArray (IArray a) -> IArray a
467 | concat as = join' {arr2 = IArray a} as freeze
468 |
469 | export
470 | Eq el => Eq (IArray el) where
471 |   (==) = sameElements {el}
472 |
473 | export
474 | Show a => Show (IArray a) where
475 |   showPrec p v = showCon p "fromList " (show $ arrayToList v)
476 |
477 | export
478 | Semigroup (IArray a) where
479 |   a1 <+> a2 = concat $ fromList [a1,a2]
480 |
481 | export
482 | Monoid (IArray a) where
483 |   neutral = fromList []
484 |
485 | export
486 | Functor IArray where
487 |   map f as = map' as freeze f
488 |
489 | export
490 | Applicative IArray where
491 |   pure = singleton
492 |   fs <*> as = concat $ map (\f => map (apply f) as) fs
493 |
494 | export
495 | Monad IArray where
496 |   join = concat
497 |
498 | export
499 | Foldable IArray where
500 |   foldr    = foldrArray
501 |   foldl    = foldlArray
502 |   null arr = size arr == 0
503 |
504 | -- No idea if this can be done more efficiently without
505 | -- going via List. Any idea to do this via direct iteration
506 | -- over the indices is highly welcome.
507 | export
508 | Traversable IArray where
509 |   traverse f arr = fromList <$> traverse f (toList arr)
510 |
511 | export
512 | Alternative IArray where
513 |   empty     = fromList []
514 |   as <|> bs = if null as then bs else as
515 |
516 | export
517 | FromString (IArray Char) where
518 |   fromString s = withArray s freeze
519 |