├── .gitignore ├── .travis.yml ├── README.md ├── applicative.nix ├── bool.nix ├── default.nix ├── fixpoints.nix ├── flake.nix ├── function.nix ├── functor.nix ├── list.nix ├── monad.nix ├── monoid.nix ├── nonempty.nix ├── nullable.nix ├── num.nix ├── optional.nix ├── path.nix ├── regex.nix ├── semigroup.nix ├── serde.nix ├── set.nix ├── string.nix ├── test ├── default.nix ├── framework.nix └── sections │ ├── bits.nix │ ├── bool.nix │ ├── default.nix │ ├── fixpoints.nix │ ├── function.nix │ ├── list.nix │ ├── nonempty.nix │ ├── num.nix │ ├── optional.nix │ ├── path.nix │ ├── regex.nix │ ├── serde.nix │ ├── set.nix │ └── string.nix ├── tuple.nix ├── types.nix └── version.nix /.gitignore: -------------------------------------------------------------------------------- 1 | result* 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: nix 2 | script: nix-build test/default.nix 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nix-std 2 | 3 | [![Build 4 | Status](https://travis-ci.org/chessai/nix-std.svg?branch=master)](https://travis-ci.org/chessai/nix-std) 5 | 6 | no-nixpkgs standard library for the nix expression language. 7 | 8 | ## Usage 9 | 10 | Fetch using plain Nix: 11 | 12 | ```nix 13 | with { 14 | std = import (builtins.fetchTarball { 15 | url = "https://github.com/chessai/nix-std/archive/v0.0.0.1.tar.gz"; 16 | sha256 = "0vglyghzj19240flribyvngmv0fyqkxl8pxzyn0sxlci8whmc9fr"; }); 17 | }; 18 | ``` 19 | 20 | Or, if using flakes, add it to your flake inputs: 21 | 22 | ```nix 23 | { 24 | inputs.nix-std.url = "github:chessai/nix-std"; 25 | outputs = { self, nix-std }: 26 | let 27 | std = nix-std.lib; 28 | in 29 | { 30 | # ... 31 | }; 32 | } 33 | ``` 34 | -------------------------------------------------------------------------------- /applicative.nix: -------------------------------------------------------------------------------- 1 | with { 2 | list = import ./list.nix; 3 | nonempty = import ./nonempty.nix; 4 | nullable = import ./nullable.nix; 5 | optional = import ./optional.nix; 6 | }; 7 | 8 | { 9 | list = list.applicative; 10 | nonempty = nonempty.applicative; 11 | nullable = nullable.applicative; 12 | optional = optional.applicative; 13 | } 14 | -------------------------------------------------------------------------------- /bool.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | optional = import ./optional.nix; 3 | }; 4 | 5 | rec { 6 | /* true :: bool 7 | */ 8 | true = builtins.true; 9 | 10 | /* false :: bool 11 | */ 12 | false = builtins.false; 13 | 14 | /* not :: bool -> bool 15 | */ 16 | not = x: !x; 17 | 18 | /* ifThenElse :: bool -> a -> a -> a 19 | */ 20 | ifThenElse = b: x: y: if b then x else y; 21 | 22 | /* toOptional :: bool -> a -> Optional a 23 | */ 24 | toOptional = b: x: if b then optional.just x else optional.nothing; 25 | 26 | /* toNullable :: bool -> a -> Nullable a 27 | */ 28 | toNullable = b: x: if b then x else null; 29 | } 30 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | rec { 2 | applicative = import ./applicative.nix; 3 | 4 | bool = import ./bool.nix; 5 | inherit (bool) true false not ifThenElse; 6 | 7 | fixpoints = import ./fixpoints.nix; 8 | inherit (fixpoints) fix; 9 | 10 | function = import ./function.nix; 11 | inherit (function) compose const flip id; 12 | 13 | functor = import ./functor.nix; 14 | 15 | list = import ./list.nix; 16 | inherit (list) map for; 17 | 18 | monad = import ./monad.nix; 19 | 20 | monoid = import ./monoid.nix; 21 | 22 | nonempty = import ./nonempty.nix; 23 | 24 | nullable = import ./nullable.nix; 25 | 26 | num = import ./num.nix; 27 | 28 | optional = import ./optional.nix; 29 | 30 | path = import ./path.nix; 31 | 32 | regex = import ./regex.nix; 33 | 34 | semigroup = import ./semigroup.nix; 35 | 36 | serde = import ./serde.nix; 37 | 38 | set = import ./set.nix; 39 | 40 | string = import ./string.nix; 41 | 42 | tuple = import ./tuple.nix; 43 | 44 | types = import ./types.nix; 45 | 46 | version = import ./version.nix; 47 | } 48 | -------------------------------------------------------------------------------- /fixpoints.nix: -------------------------------------------------------------------------------- 1 | rec { 2 | /* fix :: (a -> a) -> a 3 | */ 4 | fix = f: let x = f x; in x; 5 | 6 | /* until :: (a -> bool) -> (a -> a) -> a -> a 7 | */ 8 | until = p: f: x0: 9 | let 10 | go = x: if p x then x else go (f x); 11 | in go x0; 12 | } 13 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "No-nixpkgs standard library for the Nix expression language"; 3 | 4 | outputs = { self }: 5 | let 6 | defaultSystems = [ 7 | "aarch64-linux" 8 | "i686-linux" 9 | "x86_64-darwin" 10 | "x86_64-linux" 11 | ]; 12 | eachDefaultSystem = self.lib.set.gen defaultSystems; 13 | in 14 | { 15 | lib = import ./default.nix; 16 | checks = eachDefaultSystem (system: { 17 | nix-std-test = import ./test/default.nix { inherit system; }; 18 | }); 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /function.nix: -------------------------------------------------------------------------------- 1 | with { 2 | set = import ./set.nix; 3 | types = import ./types.nix; 4 | }; 5 | 6 | rec { 7 | /* id :: a -> a 8 | */ 9 | id = x: x; 10 | 11 | /* const :: a -> b -> a 12 | */ 13 | const = a: _: a; 14 | 15 | /* compose :: (b -> c) -> (a -> b) -> (a -> c) 16 | */ 17 | compose = bc: ab: a: bc (ab a); 18 | 19 | /* flip :: (a -> b -> c) -> b -> a -> c 20 | */ 21 | flip = f: b: a: f a b; 22 | 23 | /* not :: (a -> bool) -> a -> bool 24 | 25 | Inverts the boolean result of a function. 26 | 27 | > function.not function.id true 28 | false 29 | */ 30 | not = f: a: ! f a; 31 | 32 | /* args :: (a -> b) -> set 33 | */ 34 | args = f: 35 | if f ? __functor then f.__functionArgs or (args (f.__functor f)) 36 | else builtins.functionArgs f; 37 | 38 | /* setArgs :: set -> (a -> b) -> (a -> b) 39 | */ 40 | setArgs = args: f: set.assign "__functionArgs" args (toSet f); 41 | 42 | /* copyArgs :: (a -> b) -> (c -> b) -> (c -> b) 43 | */ 44 | copyArgs = src: dst: setArgs (args src) dst; 45 | 46 | /* toSet :: (a -> b) -> set 47 | 48 | Convert a lambda into a callable set, unless `f` already is one. 49 | 50 | > function.toSet function.id // { foo = "bar"; } 51 | { __functor = «lambda»; foo = "bar"; } 52 | */ 53 | toSet = f: if types.lambda.check f then { 54 | __functor = self: f; 55 | } else f; 56 | } 57 | -------------------------------------------------------------------------------- /functor.nix: -------------------------------------------------------------------------------- 1 | with { 2 | list = import ./list.nix; 3 | nonempty = import ./nonempty.nix; 4 | nullable = import ./nullable.nix; 5 | optional = import ./optional.nix; 6 | }; 7 | 8 | { 9 | list = list.functor; 10 | nonempty = nonempty.functor; 11 | nullable = nullable.functor; 12 | optional = optional.functor; 13 | } 14 | -------------------------------------------------------------------------------- /list.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) const flip not compose id; 4 | 5 | num = import ./num.nix; 6 | inherit (num) min max; 7 | 8 | _optional = import ./optional.nix; 9 | 10 | tuple = import ./tuple.nix; 11 | inherit (tuple) tuple2; 12 | }; 13 | 14 | rec { 15 | /* List functor object 16 | */ 17 | functor = { 18 | /* map :: (a -> b) -> [a] -> [b] 19 | */ 20 | inherit map; 21 | }; 22 | 23 | /* List applicative object 24 | */ 25 | applicative = functor // rec { 26 | /* pure :: a -> [a] 27 | */ 28 | pure = singleton; 29 | /* ap :: [a -> b] -> [a] -> [b] 30 | */ 31 | ap = lift2 id; 32 | /* lift2 :: (a -> b -> c) -> [a] -> [b] -> [c] 33 | */ 34 | lift2 = f: xs: ys: monad.bind xs (x: monad.bind ys (y: singleton (f x y))); 35 | }; 36 | 37 | /* List monad object 38 | */ 39 | monad = applicative // { 40 | /* join :: [[a]] -> [a] 41 | */ 42 | join = concat; 43 | 44 | /* bind :: [a] -> (a -> [b]) -> [b] 45 | */ 46 | bind = flip concatMap; 47 | }; 48 | 49 | monadFix = monad // { 50 | /* fix :: (a -> [a]) -> [a] 51 | */ 52 | fix = f: match (fix (compose f unsafeHead)) { 53 | nil = nil; 54 | cons = x: _: cons x (fix (compose unsafeTail f)); 55 | }; 56 | }; 57 | 58 | /* List semigroup object 59 | */ 60 | semigroup = { 61 | /* append :: [a] -> [a] -> [a] 62 | */ 63 | append = xs: ys: xs ++ ys; 64 | }; 65 | 66 | /* List monoid object 67 | */ 68 | monoid = semigroup // { 69 | /* empty :: [a] 70 | */ 71 | empty = nil; 72 | }; 73 | 74 | /* match :: [a] -> { nil :: b; cons :: a -> [a] -> b; } -> b 75 | 76 | Pattern match on a list. If the list is empty, 'nil' is run, and if it 77 | contains a value, 'cons' is run on the head and tail of the list. 78 | 79 | > list.match { nil = false; cons = true; } [] 80 | false 81 | > list.match { nil = false; cons = true; } [ 1 2 3 ] 82 | true 83 | */ 84 | match = xs: args: 85 | let u = uncons xs; 86 | in _optional.match u { 87 | nothing = args.nil; 88 | just = v: args.cons v._0 v._1; 89 | }; 90 | 91 | /* empty :: [a] -> bool 92 | 93 | Check if the list is empty. 94 | 95 | > list.empty [] 96 | true 97 | */ 98 | empty = xs: xs == []; 99 | 100 | /* @partial 101 | unsafeHead :: [a] -> a 102 | 103 | Get the first element of a list. Fails if the list is empty. 104 | 105 | > list.unsafeHead [ 1 2 3 ] 106 | 1 107 | */ 108 | unsafeHead = builtins.head; 109 | 110 | /* head :: [a] -> optional a 111 | 112 | Get the first element of a list. Returns `optional.nothing` if the list is 113 | empty. 114 | 115 | > list.head [ 1 2 3 ] 116 | { _tag = "just"; value = 1; } 117 | > list.head [] 118 | { _tag = "nothing"; } 119 | */ 120 | head = xs: 121 | if builtins.length xs > 0 122 | then _optional.just (builtins.head xs) 123 | else _optional.nothing; 124 | 125 | /* @partial 126 | unsafeTail :: [a] -> [a] 127 | 128 | Return the list minus the first element. Fails if the list is empty. 129 | 130 | > list.unsafeTail [ 1 2 3 ] 131 | [ 2 3 ] 132 | */ 133 | unsafeTail = builtins.tail; 134 | 135 | /* tail :: [a] -> optional [a] 136 | 137 | Return the list minus the first element. Returns `optional.nothing` if the 138 | list is empty. 139 | 140 | > list.tail [ 1 2 3 ] 141 | { _tag = "just "; value = [ 2 3 ]; } 142 | > list.tail [] 143 | { _tag = "nothing"; } 144 | */ 145 | tail = xs: 146 | if builtins.length xs > 0 147 | then _optional.just (builtins.tail xs) 148 | else _optional.nothing; 149 | 150 | /* @partial 151 | unsafeInit :: [a] -> [a] 152 | 153 | Return the list minus the last element. Fails if the list is empty. 154 | 155 | > list.unsafeInit [ 1 2 3 ] 156 | [ 1 2 ] 157 | */ 158 | unsafeInit = xs: 159 | if builtins.length xs > 0 160 | then slice 0 (length xs - 1) xs 161 | else builtins.throw "std.list.unsafeInit: empty list"; 162 | 163 | /* init :: [a] -> optional [a] 164 | 165 | Return the list minus the last element. Returns `optional.nothing` if the 166 | list is empty. 167 | 168 | > list.init [ 1 2 3 ] 169 | { _tag = "just"; value = [ 1 2 ]; } 170 | > list.init [] 171 | { _tag = "nothing"; } 172 | */ 173 | init = xs: 174 | if builtins.length xs > 0 175 | then _optional.just (slice 0 (length xs - 1) xs) 176 | else _optional.nothing; 177 | 178 | /* @partial 179 | unsafeLast :: [a] -> a 180 | 181 | Get the last element of a list. Fails if the list is empty. 182 | 183 | > list.unsafeLast [ 1 2 3 ] 184 | 3 185 | */ 186 | unsafeLast = xs: 187 | let len = builtins.length xs; 188 | in if len > 0 189 | then builtins.elemAt xs (len - 1) 190 | else builtins.throw "std.list.unsafeLast: empty list"; 191 | 192 | /* last :: [a] -> optional a 193 | 194 | Get the last element of a list. Returns `optional.nothing` if the list 195 | is empty. 196 | 197 | > list.last [ 1 2 3 ] 198 | { _tag = "just"; value = 3; } 199 | > list.last [] 200 | { _tag = "nothing"; } 201 | */ 202 | last = xs: index xs (builtins.length xs - 1); 203 | 204 | /* take :: int -> [a] -> [a] 205 | 206 | Take the first n elements of a list. If there are fewer than n elements, 207 | return as many elements as possible. 208 | 209 | > list.take 3 [ 1 2 3 4 5 ] 210 | [ 1 2 3 ] 211 | > list.take 30 [ 1 2 3 4 5 ] 212 | [ 1 2 3 4 5 ] 213 | */ 214 | take = n: slice 0 (max 0 n); 215 | 216 | /* drop :: int -> [a] -> [a] 217 | 218 | Return the list minus the first n elements. If there are fewer than n 219 | elements, return the empty list. 220 | 221 | > list.drop 3 [ 1 2 3 4 5 ] 222 | [ 4 5 ] 223 | > list.drop 30 [ 1 2 3 4 5 ] 224 | [] 225 | */ 226 | drop = n: slice (max 0 n) null; 227 | 228 | /* takeEnd :: int -> [a] -> [a] 229 | 230 | Take the last n elements of a list. If there are fewer than n elements, 231 | return as many elements as possible. 232 | 233 | > list.takeEnd 3 [ 1 2 3 4 5 ] 234 | [ 3 4 5 ] 235 | > list.takeEnd 30 [ 1 2 3 4 5 ] 236 | [ 1 2 3 4 5 ] 237 | */ 238 | takeEnd = n: xs: 239 | let 240 | len = length xs; 241 | n' = min len n; 242 | in slice (len - n') n' xs; 243 | 244 | /* dropEnd :: int -> [a] -> [a] 245 | 246 | Return the list minus the last n elements. If there are fewer than n 247 | elements, return the empty list. 248 | 249 | > list.dropEnd 3 [ 1 2 3 4 5 ] 250 | [ 1 2 ] 251 | > list.dropEnd 30 [ 1 2 3 4 5 ] 252 | [] 253 | */ 254 | dropEnd = n: xs: slice 0 (max 0 (length xs - n)) xs; 255 | 256 | /* length :: [a] -> int 257 | 258 | Return the length of a list. 259 | 260 | > list.length [ 1 2 3 ] 261 | 3 262 | */ 263 | length = builtins.length; 264 | 265 | /* singleton :: a -> [a] 266 | 267 | Wrap an element in a singleton list. 268 | 269 | > list.singleton 3 270 | [ 3 ] 271 | */ 272 | singleton = x: [x]; 273 | 274 | /* map :: (a -> b) -> [a] -> [b] 275 | 276 | Apply a function to every element of a list, returning the resulting list. 277 | 278 | > list.map (x: x + 1) [ 1 2 3 ] 279 | [ 2 3 4 ] 280 | */ 281 | map = builtins.map; 282 | 283 | /* for :: [a] -> (a -> b) -> [b] 284 | 285 | Like 'map', but with its arguments reversed. 286 | 287 | > list.for [ 1 2 3 ] (x: x + 1) 288 | [ 2 3 4 ] 289 | */ 290 | for = flip map; 291 | 292 | /* imap :: (int -> a -> b) -> [a] -> [b] 293 | 294 | Apply a function to every element of a list and its index, returning the 295 | resulting list. 296 | 297 | > list.imap (x: [i x]) [ 9 8 7 ] 298 | [ [ 0 9 ] [ 1 8 ] [ 2 7 ] ] 299 | */ 300 | imap = f: xs: 301 | let len = length xs; 302 | in generate (i: f i (builtins.elemAt xs i)) len; 303 | 304 | /* modifyAt :: int -> (a -> a) -> [a] -> [a] 305 | 306 | Apply a function to the nth element of a list, returning the new list. If 307 | the index is out of bounds, return the list unchanged. 308 | 309 | > list.modifyAt 1 (x: 10 * x) [ 1 2 3 ] 310 | [ 1 20 3 ] 311 | */ 312 | modifyAt = i: f: imap (j: x: if j == i then f x else x); 313 | 314 | /* setAt :: int -> a -> [a] -> [a] 315 | 316 | Insert an element as the nth element of a list, returning the new list. If 317 | the index is out of bounds, return the list unchanged. 318 | 319 | > list.setAt 1 20 [ 1 2 3 ] 320 | [ 1 20 3 ] 321 | */ 322 | setAt = i: x: modifyAt i (const x); 323 | 324 | /* insertAt :: int -> a -> [a] -> [a] 325 | 326 | Insert an element as the nth element of a list, returning the new list. If 327 | the index is out of bounds, fail with an exception. 328 | 329 | > list.insertAt 1 20 [ 1 2 3 ] 330 | [ 1 20 2 3 ] 331 | > list.insertAt 3 20 [ 1 2 3 ] 332 | [ 1 2 3 20 ] 333 | */ 334 | insertAt = i: x: xs: 335 | let 336 | len = length xs; 337 | in if i < 0 || i > len 338 | then builtins.throw "std.list.insertAt: index out of bounds" 339 | else generate 340 | (j: 341 | if j == i then 342 | x 343 | else if j < i then 344 | builtins.elemAt xs j 345 | else 346 | builtins.elemAt xs (j - 1) 347 | ) 348 | (len + 1); 349 | 350 | /* ifor :: [a] -> (int -> a -> b) -> [b] 351 | 352 | Like 'imap', but with its arguments reversed. 353 | 354 | > list.ifor[ 9 8 7 ] (x: [i x]) 355 | [ [ 0 9 ] [ 1 8 ] [ 2 7 ] ] 356 | */ 357 | ifor = flip imap; 358 | 359 | /* @partial 360 | unsafeIndex :: [a] -> int -> a 361 | 362 | Get the nth element of a list, indexed from 0. Fails if the index is out of 363 | bounds of the list. 364 | 365 | > list.unsafeIndex [ 1 2 3 ] 1 366 | 2 367 | */ 368 | unsafeIndex = builtins.elemAt; 369 | 370 | /* index :: [a] -> int -> optional a 371 | 372 | Get the nth element of a list, indexed from 0. Returns `optional.nothing` 373 | if the index is out of bounds of the list. 374 | 375 | > list.index [ 1 2 3 ] 1 376 | { _tag = "just"; value = 2; } 377 | > list.index [ 1 2 3 ] 4 378 | { _tag = "nothing"; } 379 | */ 380 | index = xs: ix: 381 | let 382 | len = builtins.length xs; 383 | in if ix < 0 || ix >= len 384 | then _optional.nothing 385 | else _optional.just (builtins.elemAt xs ix); 386 | 387 | /* concat :: [[a]] -> [a] 388 | 389 | Concatenate a list of lists. 390 | 391 | > list.concat [ [ 1 2 3 ] [ 4 5 ] [ 6 ] ] 392 | [ 1 2 3 4 5 6 ] 393 | */ 394 | concat = builtins.concatLists; 395 | 396 | /* filter :: (a -> bool) -> [a] -> [a] 397 | 398 | Apply a predicate to a list, keeping only the elements that match the 399 | predicate. 400 | 401 | > list.filter num.even [ 1 2 3 4 5 ] 402 | [ 2 4 ] 403 | */ 404 | filter = builtins.filter; 405 | 406 | /* elem :: a -> [a] -> bool 407 | 408 | Check if an element is contained in a list. 409 | 410 | > list.elem 7 [ 1 2 3 ] 411 | false 412 | > list.elem 7 [ 1 2 3 7 ] 413 | true 414 | */ 415 | elem = builtins.elem; 416 | 417 | /* notElem :: a -> [a] -> bool 418 | 419 | Check if an element is not contained in a list. 420 | 421 | > list.notElem 7 [ 1 2 3 ] 422 | true 423 | > list.notElem 7 [ 1 2 3 7 ] 424 | false 425 | */ 426 | notElem = x: xs: !(builtins.elem x xs); 427 | 428 | /* generate :: (int -> a) -> int -> [a] 429 | 430 | Generate a list given a length and a function to apply to each index. 431 | 432 | > list.generate (i: i * 2) 7 433 | [ 0 2 4 6 8 10 12 ] 434 | */ 435 | generate = builtins.genList; 436 | 437 | /* nil :: [a] 438 | 439 | The empty list, [] 440 | */ 441 | nil = []; 442 | 443 | /* cons :: a -> [a] -> [a] 444 | 445 | Prepend an element to a list 446 | 447 | > list.cons 1 [ 2 3 ] 448 | [ 1 2 3 ] 449 | */ 450 | cons = x: xs: [x] ++ xs; 451 | 452 | /* uncons :: [a] -> optional (a, [a]) 453 | 454 | Split a list into its head and tail. 455 | */ 456 | uncons = xs: if (length xs == 0) 457 | then _optional.nothing 458 | else _optional.just (tuple2 (builtins.head xs) (builtins.tail xs)); 459 | 460 | /* snoc :: [a] -> a -> [a] 461 | 462 | Append an element to a list 463 | 464 | > list.snoc [ 1 2 ] 3 465 | [ 1 2 3 ] 466 | */ 467 | snoc = xs: x: xs ++ [x]; 468 | 469 | /* foldr :: (a -> b -> b) -> b -> [a] -> b 470 | 471 | Right-associative fold over a list, starting at a given accumulator. 472 | 473 | > list.foldr (x: y: x + y) 0 [ 1 2 3 4 ] 474 | 10 475 | */ 476 | foldr = k: z0: xs: 477 | let len = length xs; 478 | go = n: 479 | if n == len 480 | then z0 481 | else k (builtins.elemAt xs n) (go (n + 1)); 482 | in go 0; 483 | 484 | /* foldl' :: (b -> a -> b) -> b -> [a] -> b 485 | 486 | Strict left-associative fold over a list, starting at a given accumulator. 487 | 488 | > list.foldl' (x: y: x + y) 0 [ 1 2 3 4 ] 489 | 10 490 | */ 491 | foldl' = builtins.foldl'; 492 | 493 | /* foldMap :: Monoid m => (a -> m) -> [a] -> m 494 | 495 | Apply a function to each element of a list, turning it into a monoid, and 496 | append all the results using the provided monoid. 497 | 498 | > list.foldMap string.monoid builtins.toJSON [ 1 2 3 4 5 ] 499 | "12345" 500 | */ 501 | foldMap = m: f: foldr (compose m.append f) m.empty; 502 | 503 | /* fold :: Monoid m => [m] -> m 504 | 505 | Append all elements of a list using a provided monoid. 506 | 507 | > list.fold monoid.max [ 1 7 3 4 5 ] 508 | 7 509 | */ 510 | fold = m: foldr m.append m.empty; 511 | 512 | /* sum :: [number] -> number 513 | 514 | Sum a list of numbers. 515 | 516 | > list.sum [ 1 2 3 4 ] 517 | 10 518 | */ 519 | sum = foldl' (x: y: builtins.add x y) 0; 520 | 521 | /* product :: [number] -> number 522 | 523 | Take the product of a list of numbers. 524 | 525 | > list.product [ 1 2 3 4 ] 526 | 24 527 | */ 528 | product = foldl' (x: y: builtins.mul x y) 1; 529 | 530 | /* concatMap :: (a -> [b]) -> [a] -> [b] 531 | 532 | Apply a function returning a list to every element of a list, and 533 | concatenate the resulting lists. 534 | 535 | > list.concatMap (x: [ x x ]) [ 1 2 3 ] 536 | [ 1 1 2 2 3 3 ] 537 | */ 538 | concatMap = builtins.concatMap; 539 | 540 | /* any :: (a -> bool) -> [a] -> bool 541 | 542 | Check if any element of a list matches a predicate. 543 | 544 | > list.any num.even [ 1 2 3 ] 545 | true 546 | > list.any num.odd [ 2 4 6 ] 547 | false 548 | */ 549 | any = builtins.any; 550 | 551 | /* all :: (a -> bool) -> [a] -> bool 552 | 553 | Check if every element of a list matches a predicate. Note that if a list 554 | is empty, the predicate matches every element vacuously. 555 | 556 | > list.all num.even [ 2 4 6 ] 557 | true 558 | > list.all num.odd [ 1 2 3 ] 559 | false 560 | */ 561 | all = builtins.all; 562 | 563 | /* none :: (a -> bool) -> [a] -> bool 564 | 565 | Check that none of the elements in a list match the given predicate. Note 566 | that if a list is empty, none of the elements match the predicate 567 | vacuously. 568 | */ 569 | none = p: xs: builtins.all (not p) xs; 570 | 571 | /* count :: (a -> bool) -> [a] -> int 572 | 573 | Count the number of times a predicate holds on the elements of a list. 574 | 575 | > list.count num.even [ 1 2 3 4 5 ] 576 | 2 577 | */ 578 | count = p: foldl' (c: x: if p x then c + 1 else c) 0; 579 | 580 | /* optional :: bool -> a -> [a] 581 | 582 | Optionally wrap an element in a singleton list. If the condition is true, 583 | return the element in a singleton list, otherwise return the empty list. 584 | 585 | > list.optional true "foo" 586 | [ "foo" ] 587 | > list.optional false "foo" 588 | [] 589 | */ 590 | optional = b: x: if b then [x] else []; 591 | 592 | /* optionals :: bool -> [a] -> [a] 593 | 594 | Optionally keep a list. If the condition is true, return the list 595 | unchanged, otherwise return an empty list. 596 | 597 | > list.optionals true [ 1 2 3 ] 598 | [ 1 2 3 ] 599 | > list.optionals false [ 1 2 3 ] 600 | [] 601 | */ 602 | optionals = b: xs: if b then xs else []; 603 | 604 | /* replicate :: int -> a -> [a] 605 | 606 | Create a list containing n copies of an element. 607 | 608 | > list.replicate 4 0 609 | [ 0 0 0 0 ] 610 | */ 611 | replicate = n: x: generate (const x) n; 612 | 613 | /* slice :: int -> nullable int -> [a] -> [a] 614 | 615 | Extract a sublist from a list given a starting position and a length. If 616 | the starting position is past the end of the list, return the empty list. 617 | If there are fewer than the requested number of elements after the starting 618 | position, take as many as possible. If the requested length is null, 619 | ignore the length and return until the end of the list. If the requested 620 | length is less than 0, the length used will be 0. 621 | 622 | Fails if the given offset is negative. 623 | 624 | > list.slice 2 2 [ 1 2 3 4 5 ] 625 | [ 3 4 ] 626 | > list.slice 2 30 [ 1 2 3 4 5 ] 627 | [ 3 4 5 ] 628 | > list.slice 1 null [ 1 2 3 4 5 ] 629 | [ 2 3 4 5 ] 630 | */ 631 | slice = offset: len: xs: 632 | if offset < 0 then 633 | throw "std.list.slice: negative start position" 634 | else 635 | let 636 | remaining = max 0 (length xs - offset); 637 | len' = if len == null then remaining else min (max len 0) remaining; 638 | in generate (i: builtins.elemAt xs (i + offset)) len'; 639 | 640 | /* range :: int -> int -> [int] 641 | 642 | Generate an ascending range from the first number to the second number, 643 | inclusive. 644 | 645 | > list.range 4 9 646 | [ 4 5 6 7 8 9 ] 647 | */ 648 | range = first: last: 649 | if first > last 650 | then [] 651 | else generate (n: first + n) (last - first + 1); 652 | 653 | /* partition :: (a -> bool) -> [a] -> ([a], [a]) 654 | 655 | Partition a list into the elements which do and do not match a predicate. 656 | 657 | > list.partition num.even [ 1 2 3 4 5 ] 658 | { _0 = [ 2 4 ]; _1 = [ 1 3 5 ]; } 659 | */ 660 | partition = p: xs: 661 | let bp = builtins.partition p xs; 662 | in tuple2 bp.right bp.wrong; 663 | 664 | /* traverse :: Applicative f => (a -> f b) -> [a] -> f [b] 665 | 666 | Apply a function to every element of a list, and sequence the results using 667 | the provided applicative functor. 668 | */ 669 | traverse = ap: f: foldr (x: ap.lift2 cons (f x)) (ap.pure nil); 670 | 671 | /* sequence :: Applicative f => [f a] -> f [a] 672 | 673 | Use the provided applicative functor to sequence every element of a list of 674 | applicatives. 675 | */ 676 | sequence = ap: foldr (x: ap.lift2 cons x) (ap.pure nil); 677 | 678 | /* zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] 679 | 680 | Zip two lists together with the provided function. The resulting list has 681 | length equal to the length of the shorter list. 682 | 683 | > list.zipWith builtins.add [ 1 2 3 ] [ 4 5 6 7 ] 684 | [ 5 7 9 ] 685 | */ 686 | zipWith = f: xs0: ys0: 687 | let len = min (length xs0) (length ys0); 688 | in generate (n: f (builtins.elemAt xs0 n) (builtins.elemAt ys0 n)) len; 689 | 690 | /* zip :: [a] -> [b] -> [(a, b)] 691 | 692 | Zip two lists together into a list of tuples. The resulting list has length 693 | equal to the length of the shorter list. 694 | 695 | > list.zip [ 1 2 3 ] [ 4 5 6 7 ] 696 | [ { _0 = 1; _1 = 4; } { _0 = 2; _1 = 5; } { _0 = 3; _1 = 6; } ] 697 | */ 698 | zip = zipWith tuple2; 699 | 700 | /* reverse :: [a] -> [a] 701 | 702 | Reverse a list. 703 | 704 | > list.reverse [ 1 2 3 ] 705 | [ 3 2 1 ] 706 | */ 707 | reverse = xs: 708 | let len = length xs; 709 | in generate (n: builtins.elemAt xs (len - n - 1)) len; 710 | 711 | /* unfold :: (b -> optional (a, b)) -> b -> [a] 712 | 713 | Build a list by repeatedly applying a function to a starting value. On each 714 | step, the function should produce a tuple of the next value to add to the 715 | list, and the value to pass to the next iteration. To finish building the 716 | list, the function should return null. 717 | 718 | > list.unfold (n: if n == 0 then optional.nothing else optional.just (tuple2 n (n - 1))) 10 719 | [ 10 9 8 7 6 5 4 3 2 1 ] 720 | */ 721 | unfold = f: x0: 722 | let 723 | go = xs: next: 724 | _optional.match next { 725 | nothing = xs; 726 | just = v: go (xs ++ [v._0]) (f v._1); 727 | }; 728 | in go [] (f x0); 729 | 730 | /* findIndex :: (a -> bool) -> [a] -> optional int 731 | 732 | Find the index of the first element matching the predicate, or 733 | `optional.nothing` if no element matches the predicate. 734 | 735 | > list.findIndex num.even [ 1 2 3 4 ] 736 | { _tag = "just"; value = 1; } 737 | > list.findIndex num.even [ 1 3 5 ] 738 | { _tag = "nothing"; } 739 | */ 740 | findIndex = pred: xs: 741 | let 742 | len = length xs; 743 | go = i: 744 | if i >= len 745 | then { _tag = "nothing"; } #_optional.nothing 746 | else if pred (builtins.elemAt xs i) 747 | then { _tag = "just"; value = i; } #_optional.just i 748 | else go (i + 1); 749 | in go 0; 750 | 751 | /* findLastIndex :: (a -> bool) -> [a] -> optional int 752 | 753 | Find the index of the last element matching the predicate, or 754 | `optional.nothing` if no element matches the predicate. 755 | 756 | > list.findLastIndex num.even [ 1 2 3 4 ] 757 | { _tag = "just"; value = 3; } 758 | > list.findLastIndex num.even [ 1 3 5 ] 759 | { _tag = "nothing"; } 760 | */ 761 | findLastIndex = pred: xs: 762 | let 763 | len = length xs; 764 | go = i: 765 | if i < 0 766 | then _optional.nothing 767 | else if pred (builtins.elemAt xs i) 768 | then _optional.just i 769 | else go (i - 1); 770 | in go (len - 1); 771 | 772 | /* find :: (a -> bool) -> [a] -> optional a 773 | 774 | Find the first element matching the predicate, or `optional.nothing` if no 775 | element matches the predicate. 776 | 777 | > list.find num.even [ 1 2 3 4 ] 778 | { _tag = "just"; value = 2; } 779 | > list.find num.even [ 1 3 5 ] 780 | { _tag = "nothing"; } 781 | */ 782 | find = pred: xs: 783 | _optional.match (findIndex pred xs) { 784 | nothing = _optional.nothing; 785 | just = i: _optional.just (builtins.elemAt xs i); 786 | }; 787 | 788 | /* findLast :: (a -> bool) -> [a] -> optional a 789 | 790 | Find the last element matching the predicate, or `optional.nothing` if no 791 | element matches the predicate. 792 | 793 | > list.find num.even [ 1 2 3 4 ] 794 | { _tag = "just"; value = 4; } 795 | > list.find num.even [ 1 3 5 ] 796 | { _tag = "nothing"; } 797 | */ 798 | findLast = pred: xs: 799 | _optional.match (findLastIndex pred xs) { 800 | nothing = _optional.nothing; 801 | just = i: _optional.just (builtins.elemAt xs i); 802 | }; 803 | 804 | /* splitAt :: int -> [a] -> ([a], [a]) 805 | 806 | Split a list at an index, returning a tuple of the list of values before 807 | and after the index. 808 | 809 | > list.splitAt 1 [ 1 2 3 ] 810 | { _0 = [ 1 ]; _1 = [ 2 3 ]; } 811 | */ 812 | splitAt = n: xs: tuple2 (take n xs) (drop n xs); 813 | 814 | /* takeWhile :: (a -> bool) -> [a] -> [a] 815 | 816 | Return the longest prefix of the list matching the predicate. 817 | 818 | > list.takeWhile num.even [ 2 4 6 9 10 11 12 14 ] 819 | [ 2 4 6 ] 820 | */ 821 | takeWhile = pred: xs: 822 | _optional.match (findIndex (not pred) xs) { 823 | nothing = xs; 824 | just = i: take i xs; 825 | }; 826 | 827 | /* dropWhile :: (a -> bool) -> [a] -> [a] 828 | 829 | Discard the longest prefix of the list matching the predicate. 830 | 831 | > list.dropWhile num.even [ 2 4 6 9 10 11 12 14 ] 832 | [ 9 10 11 12 14 ] 833 | */ 834 | dropWhile = pred: xs: 835 | _optional.match (findIndex (not pred) xs) { 836 | nothing = xs; 837 | just = i: drop i xs; 838 | }; 839 | 840 | /* takeWhileEnd :: (a -> bool) -> [a] -> [a] 841 | 842 | Return the longest suffix of the list matching the predicate. 843 | 844 | > list.takeWhileEnd num.even [ 2 4 6 9 10 11 12 14 ] 845 | [ 12 14 ] 846 | */ 847 | takeWhileEnd = pred: xs: 848 | _optional.match (findLastIndex (not pred) xs) { 849 | nothing = xs; 850 | just = i: drop (i + 1) xs; 851 | }; 852 | 853 | /* dropWhileEnd :: (a -> bool) -> [a] -> [a] 854 | 855 | Discard the longest suffix of the list matching the predicate. 856 | 857 | > list.dropWhileEnd num.even [ 2 4 6 9 10 11 12 14 ] 858 | [ 2 4 6 9 10 11 ] 859 | */ 860 | dropWhileEnd = pred: xs: 861 | _optional.match (findLastIndex (not pred) xs) { 862 | nothing = xs; 863 | just = i: take (i + 1) xs; 864 | }; 865 | 866 | /* span :: (a -> bool) -> [a] -> ([a], [a]) 867 | 868 | Find the longest prefix satisfying the given predicate, and return a tuple 869 | of this prefix and the rest of the list. 870 | 871 | > list.span num.even [ 2 4 6 9 10 11 12 14 ] 872 | { _0 = [ 2 4 6 ]; _1 = [ 9 10 11 12 14 ]; } 873 | */ 874 | span = pred: xs: 875 | _optional.match (findIndex (not pred) xs) { 876 | nothing = tuple2 xs []; 877 | just = n: splitAt n xs; 878 | }; 879 | 880 | /* break :: (a -> bool) -> [a] -> ([a], [a]) 881 | 882 | Find the longest prefix that does not satisfy the given predicate, and 883 | return a tuple of this prefix and the rest of the list. 884 | 885 | > list.break num.odd [ 2 4 6 9 10 11 12 14 ] 886 | { _0 = [ 2 4 6 ]; _1 = [ 9 10 11 12 14 ]; } 887 | */ 888 | break = pred: xs: 889 | _optional.match (findIndex pred xs) { 890 | nothing = tuple2 xs []; 891 | just = n: splitAt n xs; 892 | }; 893 | } 894 | -------------------------------------------------------------------------------- /monad.nix: -------------------------------------------------------------------------------- 1 | with { 2 | list = import ./list.nix; 3 | nonempty = import ./nonempty.nix; 4 | optional = import ./optional.nix; 5 | }; 6 | 7 | { 8 | list = list.monad; 9 | nonempty = nonempty.monad; 10 | optional = optional.monad; 11 | } 12 | -------------------------------------------------------------------------------- /monoid.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) compose id flip; 4 | 5 | semigroup = import ./semigroup.nix; 6 | 7 | imports = { 8 | list = import ./list.nix; 9 | string = import ./string.nix; 10 | nullable = import ./nullable.nix; 11 | optional = import ./optional.nix; 12 | }; 13 | }; 14 | 15 | rec { 16 | first = optional semigroup.first; 17 | 18 | last = optional semigroup.last; 19 | 20 | min = optional semigroup.min; 21 | 22 | max = optional semigroup.max; 23 | 24 | dual = monoid: semigroup.dual monoid // { 25 | empty = monoid.empty; 26 | }; 27 | 28 | endo = semigroup.endo // { 29 | empty = id; 30 | }; 31 | 32 | all = semigroup.all // { 33 | empty = true; 34 | }; 35 | 36 | and = semigroup.and // { 37 | empty = false; 38 | }; 39 | 40 | list = imports.list.monoid; 41 | 42 | string = imports.string.monoid; 43 | 44 | nullable = imports.nullable.monoid; 45 | 46 | optional = imports.optional.monoid; 47 | } 48 | -------------------------------------------------------------------------------- /nonempty.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) const flip not compose id; 4 | 5 | num = import ./num.nix; 6 | inherit (num) min max; 7 | 8 | list = import ./list.nix; 9 | optional = import ./optional.nix; 10 | }; 11 | 12 | /* type nonempty a = { head :: a, tail :: [a] } 13 | */ 14 | rec { 15 | /* List functor object 16 | */ 17 | functor = { 18 | /* map :: (a -> b) -> nonempty a -> nonempty b 19 | */ 20 | inherit map; 21 | }; 22 | 23 | /* List applicative object 24 | */ 25 | applicative = functor // rec { 26 | /* pure :: a -> nonempty a 27 | */ 28 | pure = singleton; 29 | /* ap :: nonempty a -> b -> nonempty a -> nonempty b 30 | */ 31 | ap = lift2 id; 32 | /* lift2 :: (a -> b -> c) -> nonempty a -> nonempty b -> nonempty c 33 | */ 34 | lift2 = f: xs: ys: monad.bind xs (x: monad.bind ys (y: singleton (f x y))); 35 | }; 36 | 37 | /* List monad object 38 | */ 39 | monad = applicative // rec { 40 | /* join :: nonempty (nonempty a) -> nonempty a 41 | */ 42 | join = foldl' semigroup.append; 43 | 44 | /* bind :: nonempty a -> (a -> nonempty b) -> nonempty b 45 | */ 46 | bind = { head, tail }: k: 47 | let r = k head; 48 | in make r.head (r.tail ++ list.monad.bind tail (x: toList (k x))); 49 | }; 50 | 51 | monadFix = monad // { 52 | /* fix :: (a -> nonempty a) -> nonempty a 53 | */ 54 | fix = f: match (fix (compose f head)) { 55 | cons = x: _: make x (fix (compose tail f)); 56 | }; 57 | }; 58 | 59 | /* Nonempty list semigroup object 60 | */ 61 | semigroup = { 62 | /* append :: nonempty a -> nonempty a -> nonempty a 63 | */ 64 | append = xs: ys: { 65 | head = xs.head; 66 | tail = xs.tail ++ [ys.head] ++ ys.tail; 67 | }; 68 | }; 69 | 70 | /* make :: a -> [a] -> nonempty a 71 | 72 | Make a nonempty list from a head and a possibly-empty tail. 73 | */ 74 | make = head: tail: { inherit head tail; }; 75 | 76 | /* fromList :: [a] -> optional (nonempty a) 77 | 78 | Safely convert a list into a nonempty list, returning `optional.nothing` if 79 | the list is empty. 80 | */ 81 | fromList = xs: 82 | if builtins.length xs == 0 83 | then optional.nothing 84 | else optional.just (unsafeFromList xs); 85 | 86 | /* @partial 87 | unsafeFromList :: [a] -> nonempty a 88 | 89 | Unsafely convert a list to a nonempty list. Throws an exception if the list 90 | is empty. 91 | */ 92 | unsafeFromList = xs: 93 | if builtins.length xs == 0 94 | then builtins.throw "std.nonempty.unsafeFromList: empty list" 95 | else { head = builtins.head xs; tail = builtins.tail xs; }; 96 | 97 | /* toList :: nonempty a -> [a] 98 | 99 | Converts the nonempty list to a list by forgetting the invariant that it 100 | has at least one element. 101 | */ 102 | toList = { head, tail }: [head] ++ tail; 103 | 104 | /* match :: nonempty a -> { cons :: a -> [a] -> b; } -> b 105 | 106 | Pattern match on a nonempty list. As the list is never empty, 'cons' is run 107 | on the head and tail of the list. 108 | */ 109 | match = xs: { cons }@args: args.cons xs.head xs.tail; 110 | 111 | /* head :: nonempty a -> a 112 | 113 | Get the first element of a nonempty list. 114 | */ 115 | head = x: x.head; 116 | 117 | /* tail :: nonempty a -> [a] 118 | 119 | Return the list minus the first element, which may be an empty list. 120 | */ 121 | tail = x: x.tail; 122 | 123 | /* init :: nonempty a -> [a] 124 | 125 | Return the list minus the last element, which may be an empty list. 126 | */ 127 | init = { head, tail }: 128 | if builtins.length tail == 0 129 | then [] 130 | else [head] ++ list.unsafeInit tail; 131 | 132 | /* last :: [a] -> a 133 | 134 | Get the last element of a nonempty list. 135 | */ 136 | last = { head, tail }: 137 | if builtins.length tail == 0 138 | then head 139 | else list.unsafeLast tail; 140 | 141 | /* slice :: int -> int -> [a] -> [a] 142 | 143 | Extract a sublist from a list given a starting position and a length. If 144 | the starting position is past the end of the list, return the empty list. 145 | If there are fewer than the requested number of elements after the starting 146 | position, take as many as possible. If the requested length is negative, 147 | ignore the length and return until the end of the list. 148 | 149 | Fails if the given offset is negative. 150 | */ 151 | slice = offset: len: { head, tail }: 152 | if offset < 0 153 | then builtins.throw "std.nonempty.slice: negative start position" 154 | else 155 | let 156 | remaining = max 0 (builtins.length tail + 1 - offset); 157 | len' = if len == null then remaining else min (max len 0) remaining; 158 | in list.generate 159 | (i: 160 | let i' = i + offset; 161 | in if i' == 0 162 | then head 163 | else builtins.elemAt tail (i' - 1) 164 | ) 165 | len'; 166 | 167 | /* take :: int -> nonempty a -> [a] 168 | 169 | Take the first n elements of a list. If there are less than n elements, 170 | return as many elements as possible. 171 | */ 172 | take = n: slice 0 (max 0 n); 173 | 174 | /* drop :: int -> nonempty a -> [a] 175 | 176 | Return the list minus the first n elements. If there are less than n 177 | elements, return the empty list. 178 | */ 179 | drop = n: slice (max 0 n) null; 180 | 181 | /* takeEnd :: int -> nonempty a -> [a] 182 | 183 | Take the last n elements of a list. If there are less than n elements, 184 | return as many elements as possible. 185 | */ 186 | takeEnd = n: xs: 187 | let 188 | len = length xs; 189 | n' = min len n; 190 | in slice (len - n') n' xs; 191 | 192 | /* dropEnd :: int -> nonempty a -> [a] 193 | 194 | Return the list minus the last n elements. If there are less than n 195 | elements, return the empty list. 196 | */ 197 | dropEnd = n: xs: slice 0 (max 0 (length xs - n)) xs; 198 | 199 | /* length :: nonempty a -> int 200 | 201 | Return the length of a nonempty list. 202 | */ 203 | length = { head, tail }: builtins.length tail + 1; 204 | 205 | /* singleton :: a -> nonempty a 206 | 207 | Wrap an element in a singleton list. 208 | */ 209 | singleton = head: { inherit head; tail = []; }; 210 | 211 | /* map :: (a -> b) -> nonempty a -> nonempty b 212 | 213 | Apply a function to every element of a nonempty list, returning the 214 | resulting list. 215 | */ 216 | map = f: { head, tail }: { 217 | head = f head; 218 | tail = builtins.map f tail; 219 | }; 220 | 221 | /* for :: nonempty a -> (a -> b) -> nonempty b 222 | 223 | Like 'map', but with its arguments reversed. 224 | */ 225 | for = flip map; 226 | 227 | /* imap :: (int -> a -> b) -> nonempty a -> nonempty b 228 | 229 | Apply a function to every element of a list and its index, returning the 230 | resulting list. 231 | */ 232 | imap = f: { head, tail }: { 233 | head = f 0 head; 234 | tail = list.imap (ix: x: f (ix + 1) x) tail; 235 | }; 236 | 237 | /* modifyAt :: int -> (a -> a) -> nonempty a -> nonempty a 238 | 239 | Apply a function to the nth element of a list, returning the new list. If 240 | the index is out of bounds, return the list unchanged. 241 | */ 242 | modifyAt = i: f: imap (j: x: if j == i then f x else x); 243 | 244 | /* setAt :: int -> a -> [a] -> [a] 245 | 246 | Insert an element as the nth element of a list, returning the new list. If 247 | the index is out of bounds, return the list unchanged. 248 | 249 | > list.setAt 1 20 [ 1 2 3 ] 250 | [ 1 20 3 ] 251 | */ 252 | setAt = i: x: modifyAt i (const x); 253 | 254 | /* insertAt :: int -> a -> [a] -> [a] 255 | 256 | Insert an element as the nth element of a list, returning the new list. If 257 | the index is out of bounds, fail with an exception. 258 | */ 259 | insertAt = i: x: { head, tail }: 260 | if i < 0 || i > builtins.length tail + 1 then 261 | builtins.throw "std.nonempty.insertAt: index out of bounds" 262 | else if i == 0 then 263 | { head = x; tail = [head] ++ tail; } 264 | else 265 | { inherit head; tail = list.insertAt (i - 1) x tail; }; 266 | 267 | /* ifor :: nonempty a -> (int -> a -> b) -> nonempty b 268 | 269 | Like 'imap', but with its arguments reversed. 270 | */ 271 | ifor = flip imap; 272 | 273 | /* @partial 274 | unsafeIndex :: nonempty a -> int -> a 275 | 276 | Get the nth element of a list, indexed from 0. Fails if the index is out of 277 | bounds of the list. 278 | */ 279 | unsafeIndex = { head, tail }: n: if n == 0 then head else builtins.elemAt tail (n - 1); 280 | 281 | /* index :: nonempty a -> int -> optional a 282 | 283 | Get the nth element of a list, indexed from 0. Returns `optional.nothing` 284 | if the index is out of bounds of the list. 285 | */ 286 | index = { head, tail }: n: if n == 0 then optional.just head else list.index tail (n - 1); 287 | 288 | /* filter :: (a -> bool) -> nonempty a -> [a] 289 | 290 | Apply a predicate to a list, keeping only the elements that match the 291 | predicate. 292 | */ 293 | filter = p: xs: list.filter p (toList xs); 294 | 295 | /* elem :: a -> nonempty a -> bool 296 | 297 | Check if an element is contained in a list. 298 | */ 299 | elem = e: { head, tail }: head == e || builtins.elem e tail; 300 | 301 | /* notElem :: a -> nonempty a -> bool 302 | 303 | Check if an element is not contained in a list. 304 | */ 305 | notElem = e: { head, tail }: head != e && !(builtins.elem e tail); 306 | 307 | /* cons :: a -> nonempty a -> nonempty a 308 | 309 | Prepend an element to a nonempty list 310 | */ 311 | cons = x: xs: { head = x; tail = [xs.head] ++ xs.tail; }; 312 | 313 | /* uncons :: nonempty a -> (a, [a]) 314 | 315 | Split a nonempty list into its head and tail. 316 | */ 317 | uncons = { head, tail }: { _0 = head; _1 = tail; }; 318 | 319 | /* snoc :: nonempty a -> a -> nonempty a 320 | 321 | Append an element to a nonempty list 322 | */ 323 | snoc = xs: x: { head = xs.head; tail = xs.tail ++ [x]; }; 324 | 325 | /* foldr :: (a -> b -> b) -> [a] -> b 326 | 327 | Right-associative fold over a nonempty list. 328 | */ 329 | foldr = k: { head, tail }: 330 | let tailLen = builtins.length tail; 331 | go = n: 332 | if n == tailLen - 1 333 | then builtins.elemAt tail n 334 | else k (builtins.elemAt tail n) (go (n + 1)); 335 | in if tailLen == 0 336 | then head 337 | else k head (go 0); 338 | 339 | /* foldl' :: (b -> a -> b) -> [a] -> b 340 | 341 | Strict left-associative fold over a nonempty list. 342 | */ 343 | foldl' = k: { head, tail }: list.foldl' k head tail; 344 | 345 | /* foldMap :: Semigroup m => (a -> m) -> nonempty a -> m 346 | 347 | Apply a function to each element of a list, turning it into a semigroup, 348 | and append all the results using the provided semigroup. 349 | */ 350 | foldMap = s: f: foldr (compose s.append f); 351 | 352 | /* fold :: Semigroup m => nonempty m -> m 353 | 354 | Append all elements of a list using a provided semigroup. 355 | */ 356 | fold = s: foldr s.append; 357 | 358 | /* sum :: nonempty number -> number 359 | 360 | Sum a nonempty list of numbers. 361 | */ 362 | sum = foldl' builtins.add; 363 | 364 | /* product :: [number] -> number 365 | 366 | Take the product of a list of numbers. 367 | 368 | > list.product [ 1 2 3 4 ] 369 | 24 370 | */ 371 | product = foldl' builtins.mul; 372 | 373 | /* any :: (a -> bool) -> nonempty a -> bool 374 | 375 | Check if any element of a list matches a predicate. 376 | */ 377 | any = p: { head, tail }: p head || builtins.any p tail; 378 | 379 | /* all :: (a -> bool) -> nonempty a -> bool 380 | 381 | Check if every element of a list matches a predicate. 382 | */ 383 | all = p: { head, tail }: p head && builtins.all p tail; 384 | 385 | /* none :: (a -> bool) -> [a] -> bool 386 | 387 | Check that none of the elements in a list match the given predicate. 388 | */ 389 | none = p: { head, tail }: (!p head) && builtins.all (not p) tail; 390 | 391 | /* count :: (a -> bool) -> nonempty a -> int 392 | 393 | Count the number of times a predicate holds on the elements of a list. 394 | */ 395 | count = p: { head, tail }: (if p head then 1 else 0) + list.count p tail; 396 | 397 | /* traverse :: Apply f => (a -> f b) -> nonempty a -> f (nonempty b) 398 | 399 | Apply a function to every element of a list, and sequence the results using 400 | the provided applicative functor. 401 | */ 402 | traverse = ap: f: { head, tail }: 403 | let 404 | tailLen = builtins.length tail; 405 | go = n: 406 | if n == tailLen - 1 407 | then ap.map list.singleton (f (builtins.elemAt tail n)) 408 | else ap.lift2 list.cons (f (builtins.elemAt tail n)) (go (n + 1)); 409 | in if tailLen == 0 410 | then ap.map (x: make x []) (f head) 411 | else ap.lift2 make (f head) (go 0); 412 | 413 | /* sequence :: Apply f => nonempty (f a) -> f (nonempty a) 414 | 415 | Sequence a nonempty list using the provided applicative functor. 416 | */ 417 | sequence = ap: { head, tail }: 418 | let 419 | tailLen = builtins.length tail; 420 | go = n: 421 | if n == tailLen - 1 422 | then ap.map list.singleton (builtins.elemAt tail n) 423 | else ap.lift2 list.cons (builtins.elemAt tail n) (go (n + 1)); 424 | in if tailLen == 0 425 | then ap.map (x: make x []) (f head) 426 | else ap.lift2 make head (go 0); 427 | 428 | /* zipWith :: (a -> b -> c) -> nonempty a -> nonempty b -> nonempty c 429 | 430 | Zip two lists together with the provided function. The resulting list has 431 | length equal to the length of the shorter list. 432 | */ 433 | zipWith = f: xs: ys: { 434 | head = f xs.head ys.head; 435 | tail = list.zipWith f xs.tail ys.tail; 436 | }; 437 | 438 | /* zip :: nonempty a -> nonempty b -> nonempty (a, b) 439 | 440 | Zip two lists together into a list of tuples. The resulting list has length 441 | equal to the length of the shorter list. 442 | */ 443 | zip = zipWith (x: y: { _0 = x; _1 = y; }); 444 | 445 | /* reverse :: nonempty a -> nonempty a 446 | 447 | Reverse a nonempty list. 448 | */ 449 | reverse = { head, tail }@xs: 450 | let 451 | tailLen = builtins.length tail; 452 | in if tailLen == 0 453 | then xs 454 | else { 455 | head = list.unsafeLast tail; 456 | tail = list.generate 457 | (i: 458 | if i == tailLen - 1 459 | then head 460 | else builtins.elemAt tail (tailLen - i - 2) 461 | ) 462 | tailLen; 463 | }; 464 | } 465 | -------------------------------------------------------------------------------- /nullable.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) id; 4 | optional = import ./optional.nix; 5 | }; 6 | 7 | /* 8 | type Nullable a = a | null 9 | */ 10 | 11 | rec { 12 | functor = { 13 | /* map :: (a -> b) -> Nullable a -> Nullable b 14 | */ 15 | map = f: x: 16 | if x == null 17 | then null 18 | else f x; 19 | }; 20 | 21 | applicative = functor // rec { 22 | /* pure :: a -> Nullable a 23 | */ 24 | pure = id; 25 | /* ap :: Nullable (a -> b) -> Nullable a -> Nullable b 26 | */ 27 | ap = lift2 id; 28 | /* lift2 :: (a -> b -> c) -> Nullable a -> Nullable b -> Nullable c 29 | */ 30 | lift2 = f: x: y: 31 | if x == null 32 | then null 33 | else if y == null 34 | then null 35 | else f x y; 36 | }; 37 | 38 | /* match :: Nullable a -> { nothing :: b, just :: a -> b } -> b 39 | */ 40 | match = x: { nothing, just }: 41 | if x == null 42 | then nothing 43 | else just x; 44 | 45 | semigroup = a: { 46 | append = x: y: 47 | if x == null 48 | then y 49 | else if y == null 50 | then x 51 | else a.append x y; 52 | }; 53 | 54 | monoid = a: semigroup a // { 55 | empty = null; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /num.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | list = import ./list.nix; 3 | string = import ./string.nix; 4 | optional = import ./optional.nix; 5 | 6 | tuple = import ./tuple.nix; 7 | inherit (tuple) tuple2; 8 | }; 9 | 10 | let 11 | jsonIntRE = ''-?(0|[1-9][[:digit:]]*)''; 12 | jsonNumberRE = 13 | let exponent = ''[eE]-?[[:digit:]]+''; 14 | in ''(${jsonIntRE}(\.[[:digit:]]+)?)(${exponent})?''; 15 | in rec { 16 | inherit (builtins) add mul; 17 | 18 | /* negate :: Num a => a -> a 19 | */ 20 | negate = x: 0 - x; 21 | 22 | /* abs :: Num a => a -> a 23 | */ 24 | abs = x: 25 | if x >= 0 26 | then x 27 | else negate x; 28 | 29 | /* signum :: Num a => a -> a 30 | */ 31 | signum = x: 32 | if x > 0 33 | then 1 34 | else (if x < 0 then (-1) else 0); 35 | 36 | /* min :: number -> number -> number 37 | */ 38 | min = x: y: 39 | if x <= y 40 | then x 41 | else y; 42 | 43 | /* max :: number -> number -> number 44 | */ 45 | max = x: y: 46 | if x <= y 47 | then y 48 | else x; 49 | 50 | /* compare :: number -> number -> "LT" | "EQ" | "GT" 51 | 52 | Compares two numbers and returns `"LT"` if the first is less than the 53 | second, `"EQ"` if they are equal, or `"GT"` if the first is greater than 54 | the second. 55 | */ 56 | compare = x: y: 57 | if x < y 58 | then "LT" 59 | else if x > y 60 | then "GT" 61 | else "EQ"; 62 | 63 | /* quot :: integer -> integer -> integer 64 | 65 | Integer division, truncated towards 0. This is an alias for 'builtins.div'. 66 | */ 67 | quot = builtins.div; 68 | 69 | /* rem :: integer -> integer -> integer 70 | 71 | Integer remainder. For integers m and n, this has the property that 72 | "n * (quot m n) + rem m n = m". 73 | */ 74 | rem = base: int: base - (int * (quot base int)); 75 | 76 | /* div :: integer -> integer -> integer 77 | 78 | Integer division, truncated towards negative infinity. Despite the name, 79 | note that this is not the same as 'builtins.div', which truncates towards 0. 80 | */ 81 | div = base: int: 82 | let q = quot base int; 83 | in if (base < 0) != (int < 0) 84 | then q - 1 85 | else q; 86 | 87 | /* mod :: integer -> integer -> integer 88 | 89 | Integer modulus. For integers m and n, this has the property that 90 | "n * (div m n) + mod m n = m". 91 | */ 92 | mod = base: int: base - (int * (div base int)); 93 | 94 | /* quotRem :: Integral a => a -> a -> (a, a) 95 | */ 96 | quotRem = n: d: tuple2 (quot n d) (rem n d); 97 | 98 | /* divMod :: Integral a => a -> a -> (a, a) 99 | */ 100 | divMod = n: d: tuple2 (div n d) (mod n d); 101 | 102 | /* even :: integer -> bool 103 | */ 104 | even = x: rem x 2 == 0; 105 | 106 | /* odd :: integer -> bool 107 | */ 108 | odd = x: rem x 2 != 0; 109 | 110 | /* fac :: integer -> integer 111 | 112 | Integer factorial. 113 | */ 114 | fac = n: 115 | if n < 0 116 | then throw "std.num.fac: argument is negative" 117 | else (if n == 0 then 1 else n * fac (n - 1)); 118 | 119 | /* pow :: number -> integer -> number 120 | 121 | Integer exponentiation. Note that this only handles positive integer exponents. 122 | */ 123 | pow = base0: exponent0: 124 | let pow' = x: exponent: res: 125 | if exponent == 0 126 | then res 127 | else if bits.test exponent 0 128 | then pow' x (bits.clear exponent 0) (res * x) 129 | else pow' (x * x) (bits.shiftRU exponent 1) res; 130 | in if base0 == 0 || base0 == 1 then 1 else pow' base0 exponent0 1; 131 | 132 | pi = 3.141592653589793238; 133 | 134 | /* toFloat :: number -> float 135 | 136 | Converts an integer to a floating-point number. 137 | */ 138 | toFloat = x: x + 0.0; 139 | 140 | sin = t: 141 | let 142 | # divide the domain into slices of size pi/2; t \in s * [0, pi/2) 143 | s = floor (t * 2.0 / pi); 144 | 145 | # t, wrapped to the range [0, pi/2) 146 | t' = t - s * 0.5 * pi; 147 | 148 | # reconstruction of sin(t) from sin(t') depends on the range of t: 149 | # - k * [ 0, pi/2 ) -> sin(t) = sin(t') 150 | # - k * [ pi/2, pi ) -> sin(t) = sin(pi/2 - t') 151 | # - k * [ pi, 3pi/2 ) -> sin(t) = -sin(t') 152 | # - k * [ 3pi/2, 2pi ) -> sin(t) = -sin(pi/2 - t') 153 | quadrant = mod s 4; 154 | multiplier = if quadrant == 0 || quadrant == 1 then 1 else -1; 155 | x = if quadrant == 1 || quadrant == 3 then (pi * 0.5 - t') else t'; 156 | 157 | # taylor series approximation 158 | x2 = x * x; 159 | x3 = x2 * x; 160 | x5 = x2 * x3; 161 | x7 = x2 * x5; 162 | x9 = x2 * x7; 163 | x11 = x2 * x9; 164 | x13 = x2 * x11; 165 | x15 = x2 * x13; 166 | in 167 | multiplier * 168 | (x 169 | - x3 / 6.0 170 | + x5 / 120.0 171 | - x7 / 5040.0 172 | + x9 / 362880.0 173 | - x11 / 39916800.0 174 | + x13 / 6227020800.0 175 | - x15 / 1307674368000.0); 176 | 177 | cos = t: 178 | let 179 | # divide the domain into slices of size pi/2; t \in s * [0, pi/2) 180 | s = floor (t * 2.0 / pi); 181 | 182 | # t, wrapped to the range [0, pi/2) 183 | t' = t - s * 0.5 * pi; 184 | 185 | # reconstruction of cos(t) from cos(t') depends on the range of t: 186 | # - k * [ 0, pi/2 ) -> cos(t) = cos(t') 187 | # - k * [ pi/2, pi ) -> cos(t) = -cos(pi/2 - t') 188 | # - k * [ pi, 3pi/2 ) -> cos(t) = -cos(t') 189 | # - k * [ 3pi/2, 2pi ) -> cos(t) = cos(pi/2 - t') 190 | quadrant = mod s 4; 191 | multiplier = if quadrant == 0 || quadrant == 3 then 1 else -1; 192 | x = if quadrant == 1 || quadrant == 3 then (pi * 0.5 - t') else t'; 193 | 194 | # taylor series approximation 195 | x2 = x * x; 196 | x4 = x2 * x2; 197 | x6 = x2 * x4; 198 | x8 = x2 * x6; 199 | x10 = x2 * x8; 200 | x12 = x2 * x10; 201 | x14 = x2 * x12; 202 | x16 = x2 * x14; 203 | in 204 | multiplier * 205 | (1.0 206 | - x2 / 2.0 207 | + x4 / 24.0 208 | - x6 / 720.0 209 | + x8 / 40320.0 210 | - x10 / 3628800.0 211 | + x12 / 479001600.0 212 | - x14 / 87178291200.0 213 | + x16 / 20922789888000.0); 214 | 215 | /* 216 | type Complex = { realPart :: float, imagPart :: float } 217 | */ 218 | complex = { 219 | /* conjugate :: Complex -> Complex 220 | 221 | The conjugate of a complex number. 222 | */ 223 | conjugate = c: { realPart = c.realPart; imagPart = negate c.imagPart; }; 224 | /* mkPolar :: float -> float -> Complex 225 | 226 | Form a complex number from polar components of magnitude and phase 227 | */ 228 | mkPolar = r: theta: { realPart = r * cos theta; imagPart = r * sin theta; }; 229 | 230 | /* cis :: float -> Complex 231 | 232 | @cis t@ is a complex value with magnitude 1 and phase t (modulo 2pi) 233 | */ 234 | cis = theta: { realPart = cos theta; imagPart = sin theta; }; 235 | 236 | # TODO: polar, magnitude, phase 237 | }; 238 | 239 | /* truncate :: float -> int 240 | 241 | Truncate a float to an int, rounding towards 0. 242 | */ 243 | truncate = 244 | if builtins ? floor 245 | then 246 | f: 247 | if builtins.isInt f then 248 | f 249 | else if f <= (toFloat minInt) || f >= (toFloat maxInt) then 250 | builtins.throw "std.num.truncate: integer overflow" 251 | else if f >= 0 then 252 | builtins.floor f 253 | else 254 | let 255 | p = builtins.floor f; 256 | in if p < f 257 | then p + 1 258 | else p 259 | else 260 | f: 261 | let 262 | # truncate float in range [0.0,2.0) 263 | truncate1 = x: if x < 1.0 then 0 else 1; 264 | go = x: 265 | if x < 1.0 then 266 | 0 267 | else 268 | let y = 2 * go (x / 2); 269 | in y + truncate1 (x - y); 270 | in 271 | if builtins.isInt f then 272 | f 273 | else if f <= (toFloat minInt) || f >= (toFloat maxInt) then 274 | builtins.throw "std.num.truncate: integer overflow" 275 | else if f < 0.0 then 276 | -go (-f) 277 | else 278 | go f; 279 | 280 | /* floor :: float -> int 281 | 282 | Floor a floating point number, rounding towards negative infinity. 283 | */ 284 | floor = builtins.floor or (f: truncate (if f < 0 then f - 1 else f)); 285 | 286 | /* ceil :: float -> int 287 | 288 | Ceiling a floating point number, rounding towards positive infinity. 289 | */ 290 | ceil = builtins.ceil or (f: truncate (if f > 0 then f + 1 else f)); 291 | 292 | /* round :: float -> int 293 | 294 | Round a floating-point number to the nearest integer, biased away from 0. 295 | */ 296 | round = f: signum f * floor (abs f + 0.5); 297 | 298 | /* tryParseInt :: string -> optional int 299 | 300 | Attempt to parse a string into an int. Returns `optional.nothing` on an 301 | unsuccessful parse. 302 | */ 303 | tryParseInt = x: 304 | let 305 | # fromJSON aborts on invalid JSON values; check that it matches first 306 | matches = builtins.match jsonIntRE x != null; 307 | res = builtins.fromJSON x; 308 | in if matches && builtins.isInt res 309 | then optional.just res 310 | else optional.nothing; 311 | 312 | /* @partial 313 | parseInt :: string -> int 314 | 315 | Attempt to parse a string into an int. If parsing fails, throw an 316 | exception. 317 | */ 318 | parseInt = x: optional.match (tryParseInt x) { 319 | nothing = throw "std.num.parseInt: failed to parse"; 320 | just = res: res; 321 | }; 322 | 323 | /* tryParseFloat :: string -> optional float 324 | 325 | Attempt to parse a string into a float. Returns `optional.nothing` on an 326 | unsuccessful parse. 327 | */ 328 | tryParseFloat = x: 329 | let 330 | # fromJSON aborts on invalid JSON values; check that it matches first 331 | matches = builtins.match jsonNumberRE x != null; 332 | res = builtins.fromJSON x; 333 | in if matches && (builtins.isFloat res || builtins.isInt res) 334 | then optional.just res 335 | else optional.nothing; 336 | 337 | /* @partial 338 | parseFloat :: string -> float 339 | 340 | Attempt to parse a string into a float. If parsing fails, throw an 341 | exception. 342 | */ 343 | parseFloat = x: optional.match (tryParseFloat x) { 344 | nothing = throw "std.num.parseFloat: failed to parse"; 345 | just = res: res; 346 | }; 347 | 348 | /* toBaseDigits :: int -> int -> [int] 349 | 350 | Convert an int to a list of digits in the given base. The most significant 351 | digit is the first element of the list. 352 | 353 | Note: this only works on positive numbers. 354 | */ 355 | toBaseDigits = radix: x: 356 | if x < 0 357 | then throw "std.num.toBaseDigits: argument is negative" 358 | else if radix == 1 359 | then list.replicate x 1 360 | else 361 | let 362 | go = xs: n: 363 | if n < radix 364 | then list.cons n xs 365 | else 366 | let qr = quotRem n radix; 367 | in go (list.cons (qr._1) xs) qr._0; 368 | in go [] x; 369 | 370 | /* fromBaseDigits :: int -> [int] -> int 371 | 372 | Convert a list of digits in the given base into an int. The most 373 | significant digit is the first element of the list. 374 | */ 375 | fromBaseDigits = radix: list.foldl' (acc: n: acc * radix + n) 0; 376 | 377 | /* toHexString :: int -> string 378 | */ 379 | toHexString = x: 380 | let toHexDigit = string.unsafeIndex "0123456789abcdef"; 381 | in string.concatMap toHexDigit (toBaseDigits 16 x); 382 | 383 | /* gcd :: int -> int -> int 384 | 385 | Computes the greatest common divisor of two integers. 386 | */ 387 | gcd = x: y: 388 | let gcd' = a: b: if b == 0 then a else gcd' b (rem a b); 389 | in gcd' (abs x) (abs y); 390 | 391 | /* lcm :: int -> int -> int 392 | 393 | Computes the least common multiple of two integers. 394 | */ 395 | lcm = x: y: 396 | if x == 0 || y == 0 397 | then 0 398 | else abs (quot x (gcd x y) * y); 399 | 400 | /* clamp :: num -> num -> num 401 | 402 | Clamp the value of the third argument to be between the first two 403 | arguments, so that the result is lower-bounded by the first argument and 404 | upper-bounded by the second. 405 | */ 406 | clamp = lo: hi: x: min (max lo x) hi; 407 | 408 | /* maxInt :: int 409 | 410 | The highest representable positive integer. 411 | */ 412 | maxInt = 9223372036854775807; # 2^63 - 1 413 | 414 | /* minInt :: int 415 | 416 | The lowest representable negative integer. 417 | */ 418 | minInt = maxInt + 1; # relies on 2's complement representation 419 | 420 | bits = 421 | let 422 | # table of (bit n) for 0 <= n <= (sizeof(int) - 1) 423 | powtab = [ 424 | 1 2 4 8 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 425 | 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 426 | 67108864 134217728 268435456 536870912 1073741824 2147483648 4294967296 427 | 8589934592 17179869184 34359738368 68719476736 137438953472 274877906944 428 | 549755813888 1099511627776 2199023255552 4398046511104 8796093022208 429 | 17592186044416 35184372088832 70368744177664 140737488355328 430 | 281474976710656 562949953421312 1125899906842624 2251799813685248 431 | 4503599627370496 9007199254740992 18014398509481984 36028797018963968 432 | 72057594037927936 144115188075855872 288230376151711744 433 | 576460752303423488 1152921504606846976 2305843009213693952 434 | 4611686018427387904 (9223372036854775807 + 1) 435 | ]; 436 | # De Bruijn multiplication lookup table, used for bitScanReverse 437 | debruijn = [ 438 | 0 47 1 56 48 27 2 60 439 | 57 49 41 37 28 16 3 61 440 | 54 58 35 52 50 42 21 44 441 | 38 32 29 23 17 11 4 62 442 | 46 55 26 59 40 36 15 53 443 | 34 51 20 43 31 22 10 45 444 | 25 39 14 33 19 30 9 24 445 | 13 18 8 12 7 6 5 63 446 | ]; 447 | in rec { 448 | /* bitSize :: int 449 | 450 | The number of bits used to represent an integer in Nix. 451 | */ 452 | bitSize = 64; 453 | 454 | /* bitAnd :: int -> int -> int 455 | 456 | Computes the bitwise AND of the 2's complement binary representations 457 | of the given numbers. 458 | */ 459 | bitAnd = builtins.bitAnd; 460 | 461 | /* bitOr :: int -> int -> int 462 | 463 | Computes the bitwise OR of the 2's complement binary representations of 464 | the given numbers. 465 | */ 466 | bitOr = builtins.bitOr; 467 | 468 | /* bitXor :: int -> int -> int 469 | 470 | Computes the bitwise XOR (exclusive OR) of the 2's complement binary 471 | representations of the given numbers. 472 | */ 473 | bitXor = builtins.bitXor; 474 | 475 | /* bitNot :: int -> int 476 | 477 | Computes the bitwise NOT (complement) of the 2's complement binary 478 | representation of the given number. 479 | */ 480 | bitNot = builtins.bitXor (-1); # -1 is all 1's in 2's complement 481 | 482 | /* bit :: int -> bool 483 | 484 | Gives the number whose 2's complement binary representation contains a 485 | single bit set at the given index, in which index 0 corresponds to the 486 | least-significant bit. 487 | */ 488 | bit = n: shiftL 1 n; 489 | 490 | /* set :: int -> int -> bool 491 | 492 | Sets the bit in the provided number at the provided index in its 2's 493 | complement binary representation, in which index 0 corresponds to the 494 | least-significant bit. 495 | */ 496 | set = x: n: bitOr x (bit n); 497 | 498 | /* clear :: int -> int -> bool 499 | 500 | Clears the bit in the provided number at the provided index in its 2's 501 | complement binary representation, in which index 0 corresponds to the 502 | least-significant bit. "Clear" means the bit is unset; an unset bit 503 | will remain unset, and a set bit will be unset. 504 | */ 505 | clear = x: n: bitAnd x (bitNot (bit n)); 506 | 507 | /* toggle :: int -> int -> bool 508 | 509 | Toggles the bit in the provided number at the provided index in its 2's 510 | complement binary representation, in which index 0 corresponds to the 511 | least-significant bit. "Toggle" means the bit is inverted; an unset bit 512 | will be set, and a set bit will be unset. 513 | */ 514 | toggle = x: n: bitXor x (bit n); 515 | 516 | /* test :: int -> int -> bool 517 | 518 | Tests the given number to see if the bit at the provided index in its 519 | 2's complement binary representation, in which index 0 corresponds to 520 | the least-significant bit, is set. 521 | */ 522 | test = x: n: bitAnd x (bit n) != 0; 523 | 524 | /* shiftL :: int -> int -> int 525 | 526 | Perform a left shift of the bits in the 2's complement binary 527 | representation of the given number by the provided number of places. 528 | 529 | If the number of places is larger than the bitsize, the result will 530 | always be 0. 531 | 532 | If the number of places is negative, a signed right shift will be 533 | performed. 534 | */ 535 | shiftL = x: n: 536 | if n == 0 537 | then x 538 | else if n < 0 539 | then shiftR x (-n) 540 | else if n >= bitSize 541 | then 0 542 | else x * builtins.elemAt powtab n; 543 | 544 | /* shiftLU :: int -> int -> int 545 | 546 | Perform a left shift of the bits in the 2's complement binary 547 | representation of the given number by the provided number of places. 548 | For positive shift values, this is equivalent to `shiftL`. 549 | 550 | If the number of places is larger than the bitsize, the result will 551 | always be 0. 552 | 553 | If the number of places is negative, an unsigned right shift will be 554 | performed. 555 | */ 556 | shiftLU = x: n: 557 | if n == 0 558 | then x 559 | else if n < 0 560 | then shiftRU x (-n) 561 | else if n >= bitSize 562 | then 0 563 | else x * builtins.elemAt powtab n; 564 | 565 | /* shiftR :: int -> int -> int 566 | 567 | Perform a signed (arithmetic) right shift of the bits in the 2's 568 | complement binary representation of the given number by the provided 569 | number of places. It is unsigned in that the new bits filled in on the 570 | left will always match the sign bit before shifting, preserving the 571 | sign of the number. 572 | 573 | If the number of places is larger than the bitsize, the result will 574 | always be 0 if the input was positive, or -1 otherwise. 575 | */ 576 | shiftR = x: n: 577 | if n == 0 578 | then x 579 | else if n < 0 580 | then shiftL x (-n) 581 | else if n >= bitSize 582 | then (if x < 0 then -1 else 0) 583 | else if x < 0 584 | then ((x + minInt) / (builtins.elemAt powtab n)) - builtins.elemAt powtab (63 - n) 585 | else x / builtins.elemAt powtab n; 586 | 587 | /* shiftRU :: int -> int -> int 588 | 589 | Perform an unsigned (logical) right shift of the bits in the 2's 590 | complement binary representation of the given number by the provided 591 | number of places. It is unsigned in that the new bits filled in on the 592 | left will always be 0. 593 | 594 | If the number of places is larger than the bitsize, the result will 595 | always be 0. 596 | */ 597 | shiftRU = x: n: 598 | if n == 0 599 | then x 600 | else if n < 0 601 | then shiftL x (-n) 602 | else if n >= bitSize 603 | then 0 604 | else if x < 0 605 | then ((x + minInt) / (builtins.elemAt powtab n)) + builtins.elemAt powtab (63 - n) 606 | else x / builtins.elemAt powtab n; 607 | 608 | /* rotateL :: int -> int -> int 609 | 610 | Perform a left rotation of the bits in the 2's complement binary 611 | representation of the given number by the provided number of places. 612 | */ 613 | rotateL = x: n: 614 | let n' = mod n bitSize; 615 | in bitOr (shiftL x n') (shiftRU x (bitSize - n')); 616 | 617 | /* rotateR :: int -> int -> int 618 | 619 | Perform a right rotation of the bits in the 2's complement binary 620 | representation of the given number by the provided number of places. 621 | */ 622 | rotateR = x: n: 623 | let n' = mod n bitSize; 624 | in bitOr (shiftRU x n') (shiftL x (bitSize - n')); 625 | 626 | /* popCount :: int -> int 627 | 628 | Counts the number of set bits in the 2's complement binary 629 | representation of the given integer. 630 | */ 631 | popCount = x0: 632 | # NOTE: this is hardcoded based on 'bitSize'. 633 | # 634 | # We divide-and-conquer, starting by counting the number of set bits in 635 | # every pair of bits, then adding every pair of pairs, and so on. 636 | # The constants follow the following pattern: 637 | # - 0x5555555555555555 and 0xAAAAAAAAAAAAAAAA (every other bit) 638 | # - 0x3333333333333333 and 0xCCCCCCCCCCCCCCCC (every other two bits) 639 | # - 0x0F0F0F0F0F0F0F0F and 0xF0F0F0F0F0F0F0F0 (every other four bits) 640 | # - ... 641 | # - 0x00000000FFFFFFFF and 0xFFFFFFFF00000000 642 | let 643 | x1 = (bitAnd x0 6148914691236517205) + shiftRU (bitAnd x0 (-6148914691236517206)) 1; 644 | x2 = (bitAnd x1 3689348814741910323) + shiftRU (bitAnd x1 (-3689348814741910324)) 2; 645 | x3 = (bitAnd x2 1085102592571150095) + shiftRU (bitAnd x2 (-1085102592571150096)) 4; 646 | x4 = (bitAnd x3 71777214294589695) + shiftRU (bitAnd x3 (-71777214294589696)) 8; 647 | x5 = (bitAnd x4 281470681808895) + shiftRU (bitAnd x4 (-281470681808896)) 16; 648 | x6 = (bitAnd x5 4294967295) + shiftRU (bitAnd x5 (-4294967296)) 32; 649 | in x6; 650 | 651 | /* bitScanForward :: int -> int 652 | 653 | Computes the index of the lowest set bit in the 2's complement 654 | binary representation of the given integer, where index 0 corresponds 655 | to the least-significant bit. If there are no bits set, the index will 656 | be equal to `num.bits.bitSize`. 657 | */ 658 | bitScanForward = x0: 659 | if x0 == 0 660 | then 64 661 | else popCount (bitAnd x0 (-x0) - 1); 662 | 663 | /* countTrailingZeros :: int -> int 664 | 665 | Computes the number of zeros after (in place values less than) the 666 | lowest set bit in the 2's complement binary representation of the given 667 | integer, where index 0 corresponds to the least-significant bit. If 668 | there are no bits set, the result will be equal to `num.bits.bitSize`. 669 | */ 670 | countTrailingZeros = x0: 671 | if x0 == 0 672 | then 64 673 | else bitScanForward x0; 674 | 675 | /* bitScanReverse :: int -> int 676 | 677 | Computes the index of the highest set bit in the 2's complement 678 | representation of the given integer, where index 0 corresponds to the 679 | least-significant bit. If there are no bits set, the index will be 680 | equal to `num.bits.bitSize`. 681 | */ 682 | bitScanReverse = x0: 683 | let 684 | x1 = bitOr x0 (x0 / 2); 685 | x2 = bitOr x1 (x1 / 4); 686 | x3 = bitOr x2 (x2 / 16); 687 | x4 = bitOr x3 (x3 / 256); 688 | x5 = bitOr x4 (x4 / 65536); 689 | x6 = bitOr x5 (x5 / 4294967296); 690 | in 691 | if x0 == 0 692 | then 64 693 | else if x0 < 0 # MSB is set 694 | then 63 695 | else builtins.elemAt debruijn (shiftRU (x6 * 285870213051386505) 58); 696 | 697 | /* countLeadingZeros :: int -> int 698 | 699 | Computes the number of zeros before (in place values greater than) the 700 | highest set bit in the 2's complement binary representation of the 701 | given integer, where index 0 corresponds to the least-significant bit. 702 | If there are no bits set, the result will be equal to 703 | `num.bits.bitSize`. 704 | */ 705 | countLeadingZeros = x0: 706 | if x0 == 0 707 | then 64 708 | else bitXor (bitScanReverse x0) 63; 709 | }; 710 | } 711 | -------------------------------------------------------------------------------- /optional.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) id; 4 | }; 5 | 6 | /* 7 | type optional a = { _tag :: "nothing" } | { _tag :: "just", value :: Nullable a } 8 | */ 9 | 10 | rec { 11 | /* nothing :: Optional a 12 | */ 13 | nothing = { _tag = "nothing"; }; 14 | 15 | /* just :: a -> Optional a 16 | */ 17 | just = x: { _tag = "just"; value = x; }; 18 | 19 | functor = { 20 | /* map :: (a -> b) -> Optional a -> Optional b 21 | */ 22 | map = f: x: 23 | if x._tag == "nothing" 24 | then nothing 25 | else just (f x.value); 26 | }; 27 | 28 | applicative = functor // rec { 29 | /* pure :: a -> Optional a 30 | */ 31 | pure = just; 32 | /* ap :: Optional (a -> b) -> Optional a -> Optional b 33 | */ 34 | ap = lift2 id; 35 | /* lift2 :: (a -> b -> c) -> Optional a -> Optional b -> Optional c 36 | */ 37 | lift2 = f: x: y: 38 | if x._tag == "nothing" 39 | then nothing 40 | else if y._tag == "nothing" 41 | then nothing 42 | else just (f x.value y.value); 43 | }; 44 | 45 | monad = applicative // { 46 | /* join :: Optional (Optional a) -> Optional a 47 | */ 48 | join = m: match m { 49 | nothing = { _tag = "nothing"; }; 50 | just = x: { _tag = "just"; value = x; }; 51 | }; 52 | 53 | /* bind :: Optional a -> (a -> Optional b) -> Optional b 54 | */ 55 | bind = m: k: match m { 56 | nothing = { _tag = "nothing"; }; 57 | just = k; 58 | }; 59 | }; 60 | 61 | /* `optional.semigroup` recovers a monoid from a semigroup by adding 62 | `optional.nothing` as the empty element. The semigroup's append will simply 63 | discard nothings in favor of other elements. 64 | */ 65 | semigroup = a: { 66 | append = x: y: 67 | if x._tag == "nothing" 68 | then y 69 | else if y._tag == "nothing" 70 | then x 71 | else { _tag = "just"; value = a.append x.value y.value; }; 72 | }; 73 | 74 | /* `optional.monoid` recovers a monoid from a semigroup by adding 75 | `optional.nothing` as the empty element. 76 | */ 77 | monoid = a: semigroup a // { 78 | empty = { _tag = "nothing"; }; 79 | }; 80 | 81 | /* match :: Optional a -> { nothing :: b, just :: a -> b } -> b 82 | */ 83 | match = x: con: 84 | if x._tag == "nothing" 85 | then con.nothing 86 | else con.just x.value; 87 | 88 | /* fromNullable :: nullable a -> optional a 89 | */ 90 | fromNullable = x: 91 | if x == null 92 | then nothing 93 | else just x; 94 | 95 | /* toNullable :: optional a -> nullable a 96 | */ 97 | toNullable = x: x.value or null; 98 | 99 | /* optional a -> bool 100 | */ 101 | isJust = x: x._tag == "just"; 102 | 103 | /* optional a -> bool 104 | */ 105 | isNothing = x: x._tag == "nothing"; 106 | } 107 | -------------------------------------------------------------------------------- /path.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | string = import ./string.nix; 3 | bool = import ./bool.nix; 4 | types = import ./types.nix; 5 | }; 6 | 7 | rec { 8 | /* baseName :: string | path -> string 9 | 10 | Returns everything following the final slash in a path, without 11 | context. Typically, this corresponds to the file or directory name. 12 | 13 | > path.baseName "/a/b/c" 14 | "c" 15 | > path.baseName /a/b/c 16 | "c" 17 | */ 18 | baseName = p: builtins.baseNameOf ( 19 | if types.path.check p then p 20 | # NOTE: the builtin's behaviour is inconsistent when passed a path vs string, and 21 | # it is nonsensical to retain string context for the stripped-off filename anyway. 22 | else builtins.unsafeDiscardStringContext (toString p) 23 | ); 24 | 25 | /* dirName :: string -> string 26 | 27 | Returns the directory part of the path-like string, without context. 28 | 29 | > path.dirName "/a/b/c" 30 | "/a/b" 31 | > path.dirName "a/b/c" 32 | "a/b" 33 | */ 34 | dirName = p: builtins.dirOf (builtins.unsafeDiscardStringContext (toString p)); 35 | 36 | /* parent :: path -> path 37 | parent :: string -> string 38 | 39 | Returns the directory part of the path. 40 | 41 | > path.dirName "/a/b/c" 42 | "/a/b" 43 | > path.dirName /a/b/c 44 | /a/b 45 | */ 46 | parent = builtins.dirOf; 47 | 48 | /* fromString :: string -> optional path 49 | 50 | Casts a string into a path type. Returns `optional.nothing` 51 | if the path is not absolute (it must start with a `/` to be valid). 52 | 53 | > path.fromString "/a/b/c" 54 | { _tag = "just"; value = /a/b/c; } 55 | > path.fromString "a/b/c" 56 | { _tag = "nothing"; } 57 | */ 58 | fromString = s: bool.toOptional (string.hasPrefix "/" s) (unsafeFromString s); 59 | 60 | /* unsafeFromString :: string -> path 61 | 62 | Casts a string into a path type. The resulting path will be incorrect if 63 | a relative path is provided. 64 | 65 | > path.unsafeFromString "/a/b/c" 66 | /a/b/c 67 | */ 68 | unsafeFromString = s: /. + builtins.unsafeDiscardStringContext (toString s); 69 | } 70 | -------------------------------------------------------------------------------- /regex.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | string = import ./string.nix; 3 | list = import ./list.nix; 4 | optional = import ./optional.nix; 5 | function = import ./function.nix; 6 | inherit (function) not; 7 | }; 8 | 9 | rec { 10 | /* escape :: string -> regex 11 | 12 | Turn a string into a regular expression matching the exact string. Escapes 13 | any special characters used in regular expressions. 14 | 15 | > regex.escape "a+b" 16 | "a\\+b" 17 | 18 | For example, to see if a string is contained in another string: 19 | 20 | > optional.isJust (regex.firstMatch (regex.escape "a+b") "a+b+c") 21 | true 22 | */ 23 | escape = string.escape ["\\" "^" "$" "." "+" "*" "?" "|" "(" ")" "[" "{" "}"]; 24 | 25 | /* capture :: regex -> regex 26 | 27 | Turn a regex into a capturing regex. Simply wraps the entire regex in a 28 | capture group. 29 | 30 | > regex.capture "foo" 31 | "(foo)" 32 | */ 33 | capture = re: "(${re})"; 34 | 35 | /* match :: regex -> string -> optional [nullable string] 36 | 37 | Test if a string matches a regular expression exactly. The output is 38 | `optional.nothing` if the string does not match the regex, or a list of 39 | capture groups if it does. 40 | 41 | Fails if the provided regex is invalid. 42 | 43 | > regex.match "([[:alpha:]]+)([[:digit:]]+)" "foo123" 44 | { _tag = "just"; value = [ "foo" "123" ]; } 45 | > regex.match "([[:alpha:]]+)([[:digit:]]+)" "foobar" 46 | { _tag = "nothing"; } 47 | 48 | To check whether or not a string matches a regex, simply check if the 49 | result of 'match' is non-null: 50 | 51 | > optional.isJust (regex.match "[[:digit:]]+" "123") 52 | true 53 | 54 | Note that using '+' or '*' on a capture group will only retain the last 55 | occurrence. If you use '*' on a capture group with zero occurrences, that 56 | capture group will have a null result: 57 | 58 | > regex.match "([[:digit:]])*" "123" 59 | { _tag = "just"; value = [ "3" ]; } 60 | > regex.match "([[:digit:]])*" "" 61 | { _tag = "just"; value = [ null ]; } 62 | */ 63 | match = re: str: optional.fromNullable (builtins.match re str); 64 | 65 | /* allMatches :: regex -> string -> [string] 66 | 67 | Find all occurrences (non-overlapping) of a regex in a string. 68 | 69 | Fails if the provided regex is invalid. 70 | 71 | > regex.allMatches "[[:digit:]]+" "foo 123 bar 456" 72 | [ "123" "456" ] 73 | */ 74 | allMatches = regex: str: 75 | list.concatMap (g: if builtins.isList g then [(builtins.head g)] else []) (split (capture regex) str); 76 | 77 | /* firstMatch :: regex -> string -> optional string 78 | 79 | Returns the first occurrence of the pattern in the string, or 80 | `optional.nothing` if it does not occur. 81 | 82 | > regex.firstMatch "[aeiou]" "foobar" 83 | { _tag = "just"; value = "o"; } 84 | > regex.firstMatch "[aeiou]" "xyzzyx" 85 | { _tag = "nothing"; } 86 | */ 87 | firstMatch = regex: str: 88 | let res = split (capture regex) str; 89 | in if list.length res > 1 90 | then optional.just (builtins.head (builtins.elemAt res 1)) 91 | else optional.nothing; 92 | 93 | /* lastMatch :: regex -> string -> optional string 94 | 95 | Returns the last occurrence of the pattern in the string, or 96 | `optional.nothing` if it does not occur. 97 | 98 | > regex.lastMatch "[aeiou]" "foobar" 99 | { _tag = "just"; value = "a"; } 100 | > regex.lastMatch "[aeiou]" "xyzzyx" 101 | { _tag = "nothing"; } 102 | */ 103 | lastMatch = regex: str: 104 | let 105 | res = split (capture regex) str; 106 | len = list.length res; 107 | in if len > 1 108 | then optional.just (builtins.head (builtins.elemAt res (len - 2))) 109 | else optional.nothing; 110 | 111 | /* split :: regex -> string -> [string | [nullable string]] 112 | 113 | Split a string using a regex. The result contains interspersed delimiters 114 | as lists of capture groups from the regex; see 'match' for details on 115 | capture groups. 116 | 117 | Note that if there is a leading or trailing delimeter, the first or last 118 | element of the result respectively will be the empty string. 119 | 120 | Fails if the provided regex is invalid. 121 | 122 | > regex.split "," "1,2,3" 123 | [ "1" [ ] "2" [ ] "3" ] 124 | > regex.split "(,)" "1,2,3," 125 | [ "1" [ "," ] "2" [ "," ] "3" [ "," ] "" ] 126 | */ 127 | split = builtins.split; 128 | 129 | /* splitOn :: regex -> string -> [string] 130 | 131 | Like 'split', but drops the delimiters. 132 | 133 | Fails if the provided regex is invalid. 134 | 135 | > regex.splitOn "," "1,2,3" 136 | [ "1" "2" "3" ] 137 | */ 138 | splitOn = regex: str: 139 | list.filter (not builtins.isList) (split regex str); 140 | 141 | /* substituteWith :: regex -> ([nullable string] -> string) -> string -> string 142 | 143 | Perform regex substitution on a string. Replaces each match with the result 144 | of the given function applied to the capture groups from the regex. 145 | 146 | Fails if the provided regex is invalid. 147 | 148 | > regex.substituteWith "([[:digit:]])" (g: builtins.toJSON (builtins.fromJSON (builtins.head g) + 1)) "123" 149 | "234" 150 | */ 151 | substituteWith = regex: f: str: 152 | let replaceGroup = group: 153 | if builtins.isList group 154 | then f group 155 | else group; 156 | in string.concatMap (replaceGroup) (split regex str); 157 | 158 | /* substitute :: regex -> string -> string -> string 159 | 160 | Perform regex substitution on a string, replacing each match with the given 161 | replacement string. If the regex has capture groups, they can be referred 162 | to with references \1, \2, etc. The reference \0 refers to the entire 163 | match. 164 | 165 | Fails if the provided regex is invalid or if a reference is out of bounds 166 | for the number of capture groups. 167 | 168 | > regex.substitute "o" "a" "foobar" 169 | "faabar" 170 | > regex.substitute "[aeiou]" "\\0\\0" "foobar" 171 | "foooobaar" 172 | */ 173 | substitute = regex: replace: 174 | let 175 | subs = split ''\\([[:digit:]]+)'' replace; 176 | replaceCaptures = captures: 177 | let 178 | replaceGroup = group: 179 | if builtins.isList group 180 | then builtins.elemAt captures (builtins.fromJSON (builtins.head group)) 181 | else group; 182 | in string.concatMap replaceGroup subs; 183 | in substituteWith (capture regex) replaceCaptures; 184 | } 185 | -------------------------------------------------------------------------------- /semigroup.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) compose id flip; 4 | 5 | imports = { 6 | list = import ./list.nix; 7 | string = import ./string.nix; 8 | nullable = import ./nullable.nix; 9 | optional = import ./optional.nix; 10 | nonempty = import ./nonempty.nix; 11 | }; 12 | }; 13 | 14 | { 15 | first = { 16 | append = x: _: x; 17 | }; 18 | 19 | last = { 20 | append = _: x: x; 21 | }; 22 | 23 | min = { 24 | append = x: y: 25 | if x < y 26 | then x 27 | else y; 28 | }; 29 | 30 | max = { 31 | append = x: y: 32 | if x > y 33 | then x 34 | else y; 35 | }; 36 | 37 | dual = semigroup: { 38 | append = flip semigroup.append; 39 | }; 40 | 41 | endo = { 42 | append = compose; 43 | }; 44 | 45 | all = { 46 | append = x: y: x && y; 47 | }; 48 | 49 | and = { 50 | append = x: y: x || y; 51 | }; 52 | 53 | list = imports.list.semigroup; 54 | 55 | string = imports.string.semigroup; 56 | 57 | nonempty = imports.nonempty.semigroup; 58 | 59 | nullable = imports.nullable.semigroup; 60 | 61 | optional = imports.optional.semigroup; 62 | } 63 | -------------------------------------------------------------------------------- /serde.nix: -------------------------------------------------------------------------------- 1 | let 2 | string = import ./string.nix; 3 | list = import ./list.nix; 4 | set = import ./set.nix; 5 | in rec { 6 | /* toJSON :: Set -> JSON 7 | */ 8 | toJSON = builtins.toJSON; 9 | 10 | /* @partial 11 | fromJSON :: JSON -> Set 12 | */ 13 | fromJSON = builtins.fromJSON; 14 | 15 | /* toTOML :: Set -> TOML 16 | */ 17 | toTOML = data: 18 | let 19 | # Escape a TOML key; if it is a string that's a valid identifier, we don't 20 | # need to add quotes 21 | tomlEscapeKey = val: 22 | # Identifier regex taken from https://toml.io/en/v1.0.0-rc.1#keyvalue-pair 23 | if builtins.isString val && builtins.match "[A-Za-z0-9_-]+" val != null 24 | then val 25 | else toJSON val; 26 | 27 | # Escape a TOML value 28 | tomlEscapeValue = toJSON; 29 | 30 | # Render a TOML value that appears on the right hand side of an equals 31 | tomlValue = v: 32 | if builtins.isList v 33 | then "[${string.concatMapSep ", " tomlValue v}]" 34 | else if builtins.isAttrs v 35 | then "{${string.concatMapSep ", " (kv: tomlKV kv._0 kv._1) (set.toList v)}}" 36 | else tomlEscapeValue v; 37 | 38 | # Render an inline TOML "key = value" pair 39 | tomlKV = k: v: 40 | if v == null 41 | then "" 42 | else "${tomlEscapeKey k} = ${tomlValue v}"; 43 | 44 | # Turn a prefix like [ "foo" "bar" ] into an escaped header value like 45 | # "foo.bar" 46 | dots = string.concatMapSep "." tomlEscapeKey; 47 | 48 | # Render a TOML table with a header 49 | tomlTable = oldPrefix: k: v: 50 | let 51 | prefix = oldPrefix ++ [k]; 52 | rest = go prefix v; 53 | in "[${dots prefix}]" + string.optional (rest != "") "\n${rest}"; 54 | 55 | # Render a TOML array of attrsets using [[]] notation. 'subtables' should 56 | # be a list of attrsets. 57 | tomlTableArray = oldPrefix: k: subtables: 58 | let prefix = oldPrefix ++ [k]; 59 | in string.concatMapSep "\n\n" (v: 60 | let rest = go prefix v; 61 | in "[[${dots prefix}]]" + string.optional (rest != "") "\n${rest}") subtables; 62 | 63 | # Wrap a string in a list, yielding the empty list if the string is empty 64 | optionalNonempty = str: list.optional (str != "") str; 65 | 66 | # Render an attrset into TOML; when nested, 'prefix' will be a list of the 67 | # keys we're currently in 68 | go = prefix: attrs: 69 | let 70 | attrList = set.toList attrs; 71 | 72 | # Render values that are objects using tables 73 | tableSplit = list.partition ({ _1, ... }: builtins.isAttrs _1) attrList; 74 | tablesToml = string.concatMapSep "\n\n" 75 | (kv: tomlTable prefix kv._0 kv._1) 76 | tableSplit._0; 77 | 78 | # Use [[]] syntax only on arrays of attrsets 79 | tableArraySplit = list.partition 80 | ({ _1, ... }: builtins.isList _1 && _1 != [] && list.all builtins.isAttrs _1) 81 | tableSplit._1; 82 | tableArraysToml = string.concatMapSep "\n\n" 83 | (kv: tomlTableArray prefix kv._0 kv._1) 84 | tableArraySplit._0; 85 | 86 | # Everything else becomes bare "key = value" pairs 87 | pairsToml = string.concatMapSep "\n" (kv: tomlKV kv._0 kv._1) tableArraySplit._1; 88 | in string.concatSep "\n\n" (list.concatMap optionalNonempty [ 89 | pairsToml 90 | tablesToml 91 | tableArraysToml 92 | ]); 93 | in if builtins.isAttrs data 94 | then go [] data 95 | else builtins.throw "std.serde.toTOML: input data is not an attribute set, cannot be converted to TOML"; 96 | 97 | /* @partial 98 | fromTOML :: TOML -> Set 99 | */ 100 | fromTOML = builtins.fromTOML; 101 | } 102 | -------------------------------------------------------------------------------- /set.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | bool = import ./bool.nix; 3 | function = import ./function.nix; 4 | inherit (function) id flip compose; 5 | list = import ./list.nix; 6 | nonempty = import ./nonempty.nix; 7 | 8 | imports = { 9 | optional = import ./optional.nix; 10 | }; 11 | 12 | tuple = import ./tuple.nix; 13 | inherit (tuple) tuple2; 14 | }; 15 | 16 | rec { 17 | semigroup = { 18 | append = x: y: x // y; 19 | }; 20 | 21 | monoid = semigroup // { 22 | inherit empty; 23 | }; 24 | 25 | /* empty :: set 26 | */ 27 | empty = {}; 28 | 29 | /* assign :: key -> value -> set -> set 30 | 31 | > set.assign "foo" "bar" { } 32 | { foo = "bar"; } 33 | */ 34 | assign = k: v: r: r // { "${k}" = v; }; 35 | 36 | /* assignAt :: [key] -> value -> set -> set 37 | 38 | > set.assignAt [ "foo" "bar" ] "foobar" { } 39 | { foo.bar = "foobar"; } 40 | */ 41 | assignAt = let 42 | next = k: v: r: tail: assignAt tail v (getOr { } k r); 43 | in path: v: r: imports.optional.match (nonempty.fromList path) { 44 | just = { head, tail }: assign head (next head v r tail) r; 45 | nothing = v; 46 | }; 47 | 48 | /* getAll :: key -> [set] -> [value] 49 | 50 | > set.getAll "foo" [ { foo = "bar"; } { foo = "foo"; } ] 51 | [ "bar" "foo" ] 52 | */ 53 | getAll = builtins.catAttrs; 54 | 55 | /* get :: key -> set -> optional value 56 | 57 | > set.get "foo" { foo = "bar"; } 58 | { _tag = "just"; value = "bar"; } 59 | > set.get "foo" { } 60 | { _tag = "nothing"; } 61 | */ 62 | get = k: s: bool.toOptional (s ? "${k}") (unsafeGet k s); 63 | 64 | /* getOr :: default -> key -> set -> value 65 | 66 | > set.getOr "foobar" "foo" { foo = "bar"; } 67 | "bar" 68 | > set.getOr "foobar" "foo" { } 69 | "foobar" 70 | */ 71 | getOr = default: k: s: s."${k}" or default; 72 | 73 | /* unsafeGet :: key -> set -> value 74 | 75 | > set.unsafeGet "foo" { foo = "bar"; } 76 | "bar" 77 | */ 78 | unsafeGet = k: s: s."${k}"; 79 | 80 | /* at :: [key] -> set -> optional value 81 | 82 | > set.at [ "foo" "bar" ] { foo.bar = "foobar"; } 83 | { _tag = "just"; value = "foobar"; } 84 | > set.at [ "foo" "foo" ] { foo.bar = "foobar"; } 85 | { _tag = "nothing"; } 86 | */ 87 | at = path: s: list.foldl' (s: k: 88 | imports.optional.monad.bind s (get k) 89 | ) (imports.optional.just s) path; 90 | 91 | /* atOr :: default -> [key] -> set -> value 92 | 93 | > set.atOr "123" [ "foo" "bar" ] { foo.bar = "foobar"; } 94 | "foobar" 95 | > set.atOr "123" [ "foo" "foo" ] { foo.bar = "foobar"; } 96 | "123" 97 | */ 98 | atOr = default: path: s: imports.optional.match (at path s) { 99 | nothing = default; 100 | just = id; 101 | }; 102 | 103 | /* unsafeAt :: [key] -> set -> value 104 | 105 | > set.unsafeAt [ "foo" "bar" ] { foo.bar = "foobar"; } 106 | "foobar" 107 | */ 108 | unsafeAt = path: s: list.foldl' (flip unsafeGet) s path; 109 | 110 | /* optional :: bool -> set -> set 111 | 112 | Optionally keep a set. If the condition is true, return the set 113 | unchanged, otherwise return an empty set. 114 | 115 | > set.optional true { foo = "bar"; } 116 | { foo = "bar"; } 117 | > set.optional false { foo = "bar"; } 118 | { } 119 | */ 120 | optional = b: s: if b then s else empty; 121 | 122 | match = o: { empty, assign }: 123 | if o == {} 124 | then empty 125 | else match1 o { inherit assign; }; 126 | 127 | # O(log(keys)) 128 | match1 = o: { assign }: 129 | let k = builtins.head (keys o); 130 | v = o."${k}"; 131 | r = builtins.removeAttrs o [k]; 132 | in assign k v r; 133 | 134 | /* keys :: set -> [key] 135 | */ 136 | keys = builtins.attrNames; 137 | 138 | /* values :: set -> [value] 139 | */ 140 | values = builtins.attrValues; 141 | 142 | /* map :: (key -> value -> value) -> set -> set 143 | */ 144 | map = builtins.mapAttrs; 145 | 146 | /* mapZip :: (key -> [value] -> value) -> [set] -> set 147 | */ 148 | mapZip = let 149 | zipAttrsWithNames = names: f: sets: fromList (list.map (name: tuple2 150 | name 151 | (f name (getAll name sets)) 152 | ) names); 153 | zipAttrsWith = f: sets: zipAttrsWithNames (list.concatMap keys sets) f sets; 154 | in builtins.zipAttrsWith or zipAttrsWith; 155 | 156 | /* mapToValues :: (key -> value -> a) -> set -> [a] 157 | */ 158 | mapToValues = f: compose values (map f); 159 | 160 | /* filter :: (key -> value -> bool) -> set -> set 161 | */ 162 | filter = f: s: builtins.listToAttrs (list.concatMap (name: let 163 | value = s.${name}; 164 | in list.optional (f name value) { inherit name value; }) (keys s)); 165 | 166 | /* without :: [key] -> set -> set 167 | */ 168 | without = flip builtins.removeAttrs; 169 | 170 | /* retain :: [key] -> set -> set 171 | */ 172 | retain = keys: builtins.intersectAttrs (gen keys id); 173 | 174 | /* traverse :: Applicative f => (value -> f b) -> set -> f set 175 | */ 176 | traverse = ap: f: 177 | (flip match) { 178 | empty = ap.pure empty; 179 | assign = k: v: r: ap.lift2 id (ap.map (assign k) (f v)) (traverse ap f r); 180 | }; 181 | 182 | /* toList :: set -> [(key, value)] 183 | */ 184 | toList = s: list.map (k: tuple2 k s.${k}) (keys s); 185 | 186 | /* fromList :: [(key, value)] -> set 187 | */ 188 | fromList = xs: builtins.listToAttrs (list.map tuple.toPair xs); 189 | 190 | /* gen :: [key] -> (key -> value) -> set 191 | */ 192 | gen = keys: f: fromList (list.map (n: tuple2 n (f n)) keys); 193 | } 194 | -------------------------------------------------------------------------------- /string.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) compose const flip not; 4 | 5 | bool = import ./bool.nix; 6 | list = import ./list.nix; 7 | regex = import ./regex.nix; 8 | num = import ./num.nix; 9 | _optional = import ./optional.nix; 10 | 11 | tuple = import ./tuple.nix; 12 | inherit (tuple) tuple2; 13 | }; 14 | 15 | rec { 16 | semigroup = { 17 | append = x: y: x + y; 18 | }; 19 | 20 | monoid = semigroup // { 21 | inherit empty; 22 | }; 23 | 24 | /* empty :: string 25 | 26 | The empty string. 27 | */ 28 | empty = ""; 29 | 30 | /* @partial 31 | substring :: int -> int -> string -> string 32 | 33 | Take a substring of a string at an offset with a given length. If the 34 | offset is past the end of the string, the result will be the empty string. 35 | If there are less than the requested number of characters until the end of 36 | the string, returns as many as possible. Otherwise, if the length is 37 | negative, simply returns the rest of the string after the starting 38 | position. 39 | 40 | Fails if the starting position is negative. 41 | 42 | > string.substring 2 3 "foobar" 43 | "oba" 44 | > string.substring 4 7 "foobar" 45 | "ar" 46 | > string.substring 10 5 "foobar" 47 | "" 48 | > string.substring 2 (-1) "foobar" 49 | "obar" 50 | */ 51 | substring = builtins.substring; 52 | 53 | /* @partial 54 | unsafeIndex :: string -> int -> string 55 | 56 | Returns the nth character of a string. Fails if the index is out of bounds. 57 | 58 | > string.unsafeIndex 3 "foobar" 59 | "b" 60 | */ 61 | unsafeIndex = str: n: 62 | if n < 0 || n >= length str 63 | then throw "std.string.unsafeIndex: index out of bounds" 64 | else substring n 1 str; 65 | 66 | /* index :: string -> int -> optional string 67 | 68 | Returns the nth character of a string. Returns `optional.nothing` if the 69 | string is empty. 70 | 71 | > string.index 3 "foobar" 72 | { _tag = "just"; value = "b"; } 73 | > string.index (-1) "foobar" 74 | { _tag = "nothing"; } 75 | */ 76 | index = str: n: 77 | if n < 0 || n >= length str 78 | then _optional.nothing 79 | else _optional.just (substring n 1 str); 80 | 81 | /* length :: string -> int 82 | 83 | Compute the length of a string. 84 | 85 | > string.length "foo" 86 | 3 87 | > string.length "" 88 | 0 89 | */ 90 | length = builtins.stringLength; 91 | 92 | /* isEmpty :: string -> bool 93 | 94 | Check if a string is the empty string. 95 | 96 | > string.isEmpty "foo" 97 | false 98 | > string.isEmpty "" 99 | true 100 | */ 101 | isEmpty = str: str == ""; 102 | 103 | /* replace :: [string] -> [string] -> string -> string 104 | 105 | Replace all occurrences of each string in the first list with the 106 | corresponding string in the second list. 107 | 108 | > string.replace [ "o" "a" ] [ "u" "e" ] "foobar" 109 | "fuuber" 110 | */ 111 | replace = builtins.replaceStrings; 112 | 113 | /* concat :: [string] -> string 114 | 115 | Concatenate a list of strings. 116 | 117 | > string.concat [ "foo" "bar" ] 118 | "foobar" 119 | */ 120 | concat = concatSep ""; 121 | 122 | /* concatSep :: string -> [string] -> string 123 | 124 | Concatenate a list of strings, with a separator in between. 125 | 126 | > string.concatSep ", " [ "1" "2" "3" ] 127 | "1, 2, 3" 128 | */ 129 | concatSep = builtins.concatStringsSep; 130 | 131 | /* concatMap :: (a -> string) -> [a] -> string 132 | 133 | Apply a function to each element in a list and concatenate the resulting 134 | list of strings. 135 | 136 | > string.concatMap builtins.toJSON [ 1 2 3 ] 137 | "123" 138 | */ 139 | concatMap = f: strs: concat (list.map f strs); 140 | 141 | /* concatMapSep :: string -> (a -> string) -> [a] -> string 142 | 143 | Apply a function to each element in a list and concatenate the resulting 144 | list of strings, with a separator in between. 145 | 146 | > string.concatMapSep ", " builtins.toJSON [ 1 2 3 ] 147 | "1, 2, 3" 148 | */ 149 | concatMapSep = sep: f: strs: concatSep sep (list.map f strs); 150 | 151 | /* concatImap :: (int -> a -> string) -> [a] -> string 152 | 153 | Apply a function to each index and element in a list and concatenate the 154 | resulting list of strings. 155 | 156 | > string.concatImap (i: x: x + builtins.toJSON i) [ "foo" "bar" "baz" ] 157 | "foo0bar1baz2" 158 | */ 159 | concatImap = f: strs: concat (list.imap f strs); 160 | 161 | /* concatImapSep :: string -> (int -> a -> string) -> [a] -> string 162 | 163 | Apply a function to each index and element in a list and concatenate the 164 | resulting list of strings, with a separator in between. 165 | 166 | > string.concatImapSep "\n" (i: x: builtins.toJSON (i + 1) + ": " + x) [ "foo" "bar" "baz" ] 167 | "1: foo\n2: bar\n3: baz" 168 | */ 169 | concatImapSep = sep: f: strs: concatSep sep (list.imap f strs); 170 | 171 | /* toChars :: string -> [char] 172 | 173 | Convert a string to a list of the characters in the string. 174 | 175 | > string.toChars "foo" 176 | [ "f" "o" "o" ] 177 | */ 178 | toChars = str: list.generate (unsafeIndex str) (length str); 179 | 180 | /* fromChars :: [char] -> string 181 | 182 | Convert a list of characters to a string. 183 | 184 | > string.fromChars [ "f" "o" "o" ] 185 | "foo" 186 | */ 187 | fromChars = ls: list.foldr (c: s: c + s) "" ls; 188 | 189 | /* map :: (string -> string) -> string -> string 190 | 191 | Map over a string, applying a function to each character. 192 | */ 193 | map = f: str: concatMap f (toChars str); 194 | 195 | /* imap :: (int -> string -> string) -> string -> string 196 | 197 | Map over a string, applying a function to each character and its index. 198 | */ 199 | imap = f: str: concatImap f (toChars str); 200 | 201 | /* filter :: (string -> bool) -> string -> string 202 | 203 | Filter the characters in a string, keeping only those which match the 204 | given predicate. 205 | */ 206 | filter = pred: str: concat (list.filter pred (toChars str)); 207 | 208 | /* findIndex :: (string -> bool) -> string -> Optional int 209 | 210 | Find the index of the first character in a string matching the predicate, 211 | or return null if no such character is present. 212 | */ 213 | findIndex = pred: str: 214 | let 215 | len = length str; 216 | go = i: 217 | if i >= len 218 | then _optional.nothing 219 | else if pred (unsafeIndex str i) 220 | then _optional.just i 221 | else go (i + 1); 222 | in go 0; 223 | 224 | /* findLastIndex :: (string -> bool) -> string -> Optional int 225 | 226 | Find the index of the last character in a string matching the predicate, or 227 | return null if no such character is present. 228 | */ 229 | findLastIndex = pred: str: 230 | let 231 | len = length str; 232 | go = i: 233 | if i < 0 234 | then _optional.nothing 235 | else if pred (unsafeIndex str i) 236 | then _optional.just i 237 | else go (i - 1); 238 | in go (len - 1); 239 | 240 | /* find :: (string -> bool) -> string -> Optional string 241 | 242 | Find the first character in a string matching the predicate, or return null 243 | if no such character is present. 244 | */ 245 | find = pred: str: 246 | let i = findIndex pred str; 247 | in if i._tag == "nothing" 248 | then _optional.nothing 249 | else _optional.just (unsafeIndex str i.value); 250 | 251 | /* findLast :: (string -> bool) -> string -> Optional string 252 | 253 | Find the last character in a string matching the predicate, or return null 254 | if no such character is present. 255 | */ 256 | findLast = pred: str: 257 | let i = findLastIndex pred str; 258 | in if i._tag == "nothing" 259 | then _optional.nothing 260 | else _optional.just (unsafeIndex str i.value); 261 | 262 | /* escape :: [string] -> string -> string 263 | 264 | Backslash-escape the chars in the given list. 265 | 266 | > string.escape [ "$" ] "foo$bar" 267 | "foo\\$bar" 268 | */ 269 | escape = chars: replace chars (list.map (c: "\\${c}") chars); 270 | 271 | /* escapeShellArgs :: string -> string 272 | 273 | Escape an argument to be suitable to pass to the shell 274 | */ 275 | escapeShellArg = arg: "'${replace ["'"] ["'\\''"] (toString arg)}'"; 276 | 277 | /* escapeNixString :: string -> string 278 | 279 | Turn a string into a Nix expression representing that string 280 | */ 281 | escapeNixString = str: escape ["$"] (builtins.toJSON str); 282 | 283 | /* hasPrefix :: string -> string -> bool 284 | 285 | Test if a string starts with a given string. 286 | */ 287 | hasPrefix = pre: str: 288 | let 289 | strLen = length str; 290 | preLen = length pre; 291 | in preLen <= strLen && substring 0 preLen str == pre; 292 | 293 | /* hasSuffix :: string -> string -> bool 294 | 295 | Test if a string ends with a given string. 296 | */ 297 | hasSuffix = suf: str: 298 | let 299 | strLen = length str; 300 | sufLen = length suf; 301 | in sufLen <= strLen && substring (length str - sufLen) sufLen str == suf; 302 | 303 | /* hasInfix :: string -> string -> bool 304 | 305 | Test if a string contains a given string. 306 | */ 307 | # TODO: should this just be 'regex.firstMatch (regex.escape infix) str != null'? 308 | hasInfix = infix: str: 309 | let 310 | infixLen = length infix; 311 | strLen = length str; 312 | go = i: 313 | if i > strLen - infixLen 314 | then false 315 | else substring i infixLen str == infix || go (i + 1); 316 | in infixLen <= strLen && go 0; 317 | 318 | /* removePrefix :: string -> string -> string 319 | 320 | If the string starts with the given prefix, return the string with the 321 | prefix stripped. Otherwise, returns the original string. 322 | 323 | > removePrefix "/" "/foo" 324 | "foo" 325 | > removePrefix "/" "foo" 326 | "foo" 327 | */ 328 | removePrefix = pre: str: 329 | let 330 | preLen = length pre; 331 | strLen = length str; 332 | in if hasPrefix pre str 333 | then substring preLen (strLen - preLen) str 334 | else str; 335 | 336 | /* removeSuffix :: string -> string -> string 337 | 338 | If the string ends with the given suffix, return the string with the suffix 339 | stripped. Otherwise, returns the original string. 340 | 341 | > removeSuffix ".nix" "foo.nix" 342 | "foo" 343 | > removeSuffix ".nix" "foo" 344 | "foo" 345 | */ 346 | removeSuffix = suf: str: 347 | if hasSuffix suf str 348 | then substring 0 (length str - length suf) str 349 | else str; 350 | 351 | /* count :: string -> string -> int 352 | 353 | Count the number of times a string appears in a larger string (not counting 354 | overlapping). 355 | 356 | > string.count "oo" "foooobar" 357 | 2 358 | */ 359 | count = needle: str: list.length (regex.allMatches (regex.escape needle) str); 360 | 361 | /* optional :: bool -> string -> string 362 | 363 | Return the string if the condition is true, otherwise return the empty 364 | string. 365 | */ 366 | optional = b: str: if b then str else ""; 367 | 368 | /* @partial 369 | unsafeHead :: string -> string 370 | 371 | Return the first character of the string. 372 | 373 | Fails if the string is empty. 374 | */ 375 | unsafeHead = str: 376 | let len = length str; 377 | in if len > 0 378 | then unsafeIndex str 0 379 | else throw "std.string.unsafeHead: empty string"; 380 | 381 | /* head :: string -> optional string 382 | 383 | Return the first character of the string. 384 | 385 | Returns `optional.nothing` if the string is empty. 386 | */ 387 | head = str: 388 | let len = length str; 389 | in if len > 0 390 | then _optional.just (unsafeIndex str 0) 391 | else _optional.nothing; 392 | 393 | /* @partial 394 | unsafeTail :: string -> string 395 | 396 | Return the string minus the first character. 397 | 398 | Fails if the string is empty. 399 | */ 400 | unsafeTail = str: 401 | let len = length str; 402 | in if len > 0 403 | then substring 1 (len - 1) str 404 | else throw "std.string.unsafeTail: empty string"; 405 | 406 | /* tail :: string -> optional string 407 | 408 | Return the string minus the first character. 409 | 410 | Returns `optional.nothing` if the string is empty. 411 | */ 412 | tail = str: 413 | let len = length str; 414 | in if len > 0 415 | then _optional.just (substring 1 (len - 1) str) 416 | else _optional.nothing; 417 | 418 | /* @partial 419 | unsafeInit :: string -> string 420 | 421 | Return the string minus the last character. 422 | 423 | Fails if the string is empty. 424 | */ 425 | unsafeInit = str: 426 | let len = length str; 427 | in if len > 0 428 | then substring 0 (len - 1) str 429 | else throw "std.string.unsafeInit: empty string"; 430 | 431 | /* init :: string -> optional string 432 | 433 | Return the string minus the last character. 434 | 435 | Returns `optional.nothing` if the string is empty. 436 | */ 437 | init = str: 438 | let len = length str; 439 | in if len > 0 440 | then _optional.just (substring 0 (len - 1) str) 441 | else _optional.nothing; 442 | 443 | /* @partial 444 | unsafeLast :: string -> string 445 | 446 | Return the last character of a string. 447 | 448 | Fails if the string is empty. 449 | */ 450 | unsafeLast = str: 451 | let len = length str; 452 | in if len > 0 453 | then substring (len - 1) 1 str 454 | else throw "std.string.unsafeLast: empty string"; 455 | 456 | /* last :: string -> optional string 457 | 458 | Return the last character of a string. 459 | 460 | Returns `optional.nothing` if the string is empty. 461 | */ 462 | last = str: 463 | let len = length str; 464 | in if len > 0 465 | then _optional.just (substring (len - 1) 1 str) 466 | else _optional.nothing; 467 | 468 | /* take :: int -> string -> string 469 | 470 | Return the first n characters of a string. If less than n characters are 471 | present, take as many as possible. 472 | */ 473 | take = n: substring 0 (num.max 0 n); 474 | 475 | /* drop :: int -> string -> string 476 | 477 | Remove the first n characters of a string. If less than n characters are 478 | present, return the empty string. 479 | */ 480 | drop = n: substring (num.max 0 n) (-1); 481 | 482 | /* takeEnd :: int -> string -> string 483 | 484 | Return the last n characters of a string. If less than n characters are 485 | present, take as many as possible. 486 | */ 487 | takeEnd = n: str: 488 | let 489 | len = length str; 490 | n' = num.min len n; 491 | in substring (len - n') n' str; 492 | 493 | /* takeEnd :: int -> string -> string 494 | 495 | Remove the last n characters of a string. If less than n characters are 496 | present, return the empty string. 497 | */ 498 | dropEnd = n: str: 499 | substring 0 (num.max 0 (length str - n)) str; 500 | 501 | /* takeWhile :: (string -> bool) -> string -> string 502 | 503 | Return the longest prefix of the string that satisfies the predicate. 504 | */ 505 | takeWhile = pred: str: 506 | let n = findIndex (not pred) str; 507 | in if n._tag == "nothing" 508 | then str 509 | else take n.value str; 510 | 511 | /* dropWhile :: (string -> bool) -> string -> string 512 | 513 | Return the rest of the string after the prefix returned by 'takeWhile'. 514 | */ 515 | dropWhile = pred: str: 516 | let n = findIndex (not pred) str; 517 | in if n._tag == "nothing" 518 | then "" 519 | else drop n.value str; 520 | 521 | /* takeWhileEnd :: (string -> bool) -> string -> string 522 | 523 | Return the longest suffix of the string that satisfies the predicate. 524 | */ 525 | takeWhileEnd = pred: str: 526 | let n = findLastIndex (not pred) str; 527 | in if n._tag == "nothing" 528 | then "" 529 | else drop (n.value + 1) str; 530 | 531 | /* dropWhileEnd :: (string -> bool) -> string -> string 532 | 533 | Return the rest of the string after the suffix returned by 'takeWhileEnd'. 534 | */ 535 | dropWhileEnd = pred: str: 536 | let n = findLastIndex (not pred) str; 537 | in if n._tag == "nothing" 538 | then "" 539 | else take (n.value + 1) str; 540 | 541 | /* splitAt :: int -> string -> (string, string) 542 | 543 | Return a tuple of the parts of the string before and after the given index. 544 | */ 545 | splitAt = n: str: tuple2 (take n str) (drop n str); 546 | 547 | /* span :: (string -> bool) -> string -> (string, string) 548 | 549 | Find the longest prefix satisfying the given predicate, and return a tuple 550 | of this prefix and the rest of the string. 551 | */ 552 | span = pred: str: 553 | let n = findIndex (not pred) str; 554 | in if n._tag == "nothing" 555 | then tuple2 str "" 556 | else splitAt n.value str; 557 | 558 | /* break :: (string -> bool) -> string -> (string, string) 559 | 560 | Find the longest prefix that does not satisfy the given predicate, and 561 | return a tuple of this prefix and the rest of the string. 562 | */ 563 | break = pred: str: 564 | let n = findIndex pred str; 565 | in if n._tag == "nothing" 566 | then tuple2 str "" 567 | else splitAt n.value str; 568 | 569 | /* reverse :: string -> string 570 | 571 | Reverse a string. 572 | */ 573 | reverse = str: 574 | let len = length str; 575 | in concat (list.generate (n: substring (len - n - 1) 1 str) len); 576 | 577 | /* replicate :: int -> string -> string 578 | 579 | Return a string consisting of n copies of the given string. 580 | */ 581 | replicate = n: str: concat (list.replicate n str); 582 | 583 | /* lines :: string -> [string] 584 | 585 | Split a string on line breaks, returning the contents of each line. A line 586 | is ended by a "\n", though the last line may not have a newline. 587 | 588 | > string.lines "foo\nbar\n" 589 | [ "foo" "bar" ] 590 | > string.lines "foo\nbar" 591 | [ "foo" "bar" ] 592 | > string.lines "\n" 593 | [ "" ] 594 | > string.lines "" 595 | [] 596 | */ 597 | lines = str: 598 | let 599 | len = length str; 600 | str' = 601 | if len > 0 && substring (len - 1) 1 str == "\n" 602 | then substring 0 (len - 1) str 603 | else str; 604 | in if isEmpty str # "" is a special case; "\n"/"" don't have the same result 605 | then [] 606 | else regex.splitOn "\n" str'; 607 | 608 | /* unlines :: [string] -> string 609 | 610 | Join a list of strings into one string with each string on a separate line. 611 | 612 | > string.unlines [ "foo" "bar" ] 613 | "foo\nbar\n" 614 | > string.unlines [] 615 | "" 616 | */ 617 | unlines = concatMap (x: x + "\n"); 618 | 619 | /* words :: string -> [string] 620 | 621 | Split a string on whitespace, returning a list of each whitespace-delimited 622 | word. Leading or trailing whitespace does not affect the result. 623 | 624 | > string.words "foo \t bar " 625 | [ "foo" "bar" ] 626 | > string.words " " 627 | [] 628 | */ 629 | words = str: 630 | let stripped = strip str; 631 | in if isEmpty stripped 632 | then [] 633 | else regex.splitOn ''[[:space:]]+'' stripped; 634 | 635 | /* unwords :: [string] -> string 636 | 637 | Join a list of strings with spaces. 638 | 639 | > string.unwords [ "foo" "bar" ] 640 | "foo bar" 641 | */ 642 | unwords = concatSep " "; 643 | 644 | /* intercalate :: string -> [string] -> string 645 | 646 | Alias for 'concatSep'. 647 | */ 648 | intercalate = concatSep; 649 | 650 | /* lowerChars :: [string] 651 | 652 | A list of the lowercase characters of the English alphabet. 653 | */ 654 | lowerChars = toChars "abcdefghijklmnopqrstuvwxyz"; 655 | 656 | /* upperChars :: [string] 657 | 658 | A list of the uppercase characters of the English alphabet. 659 | */ 660 | upperChars = toChars "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 661 | 662 | /* toLower :: string -> string 663 | 664 | Convert an ASCII string to lowercase. 665 | */ 666 | toLower = replace upperChars lowerChars; 667 | 668 | /* toUpper :: string -> string 669 | 670 | Convert an ASCII string to uppercase. 671 | */ 672 | toUpper = replace lowerChars upperChars; 673 | 674 | /* isUpper :: char -> string 675 | 676 | Returns 'true' if the input character is ASCII uppercase. 677 | */ 678 | isUpper = c: list.elem c upperChars; 679 | 680 | /* isLower :: char -> string 681 | 682 | Returns 'true' if the input character is ASCII lowercase. 683 | */ 684 | isLower = c: list.elem c lowerChars; 685 | 686 | /* strip :: string -> string 687 | 688 | Remove leading and trailing whitespace from a string. 689 | */ 690 | strip = regex.substitute ''^[[:space:]]+|[[:space:]]+$'' ""; 691 | 692 | /* stripStart :: string -> string 693 | 694 | Remove leading whitespace from a string. 695 | */ 696 | stripStart = regex.substitute ''^[[:space:]]+'' ""; 697 | 698 | /* stripEnd :: string -> string 699 | 700 | Remove trailing whitespace from a string. 701 | */ 702 | stripEnd = regex.substitute ''[[:space:]]+$'' ""; 703 | 704 | /* @partial 705 | justifyLeft :: int -> string -> string 706 | 707 | Justify a string to the left to a given length, adding copies of the 708 | padding string if necessary. Does not shorten the string if the target 709 | length is shorter than the original string. If the padding string is longer 710 | than a single character, it will be cycled to meet the required length. 711 | 712 | Fails if the string to fill with is empty. 713 | 714 | > string.justifyLeft 7 "x" "foo" 715 | "fooxxxx" 716 | > string.justifyLeft 2 "x" "foo" 717 | "foo" 718 | > string.justifyLeft 7 "xyz" "foo" 719 | "fooxyzx" 720 | */ 721 | justifyLeft = n: fill: str: 722 | let 723 | strLen = length str; 724 | fillLen = length fill; 725 | padLen = num.max 0 (n - strLen); 726 | padCopies = (padLen + fillLen - 1) / fillLen; # padLen / fillLen, but rounding up 727 | padding = take padLen (replicate padCopies fill); 728 | in if fillLen > 0 729 | then str + padding 730 | else throw "std.string.justifyLeft: empty padding string"; 731 | 732 | /* @partial 733 | justifyRight :: int -> string -> string 734 | 735 | Justify a string to the right to a given length, adding copies of the 736 | padding string if necessary. Does not shorten the string if the target 737 | length is shorter than the original string. If the padding string is longer 738 | than a single character, it will be cycled to meet the required length. 739 | 740 | Fails if the string to fill with is empty. 741 | 742 | > string.justifyRight 7 "x" "foo" 743 | "xxxxfoo" 744 | > string.justifyRight 2 "x" "foo" 745 | "foo" 746 | > string.justifyRight 7 "xyz" "foo" 747 | "xyzxfoo" 748 | */ 749 | justifyRight = n: fill: str: 750 | let 751 | strLen = length str; 752 | fillLen = length fill; 753 | padLen = num.max 0 (n - strLen); 754 | padCopies = (padLen + fillLen - 1) / fillLen; # padLen / fillLen, but rounding up 755 | padding = take padLen (replicate padCopies fill); 756 | in if fillLen > 0 757 | then padding + str 758 | else throw "std.string.justifyRight: empty padding string"; 759 | 760 | /* @partial 761 | justifyCenter :: int -> string -> string 762 | 763 | Center-justify a string to a given length, adding copies of the padding 764 | string on either side if necessary. Does not shorten the string if the 765 | target length is shorter than the original string. If the padding string is 766 | longer than a single character, it will be cycled to meet the required 767 | length. If the padding is unbalanced on both sides, additional padding will 768 | go on the right. 769 | 770 | Fails if the string to fill with is empty. 771 | 772 | > string.justifyCenter 7 "x" "foo" 773 | "xxfooxx" 774 | > string.justifyCenter 2 "x" "foo" 775 | "foo" 776 | > string.justifyCenter 8 "xyz" "foo" 777 | "xyfooxyz" 778 | */ 779 | justifyCenter = n: fill: str: 780 | let 781 | strLen = length str; 782 | fillLen = length fill; 783 | leftLen = num.max 0 ((n - strLen + 1) / 2); # bias when left padding 784 | rightLen = num.max 0 ((n - strLen) / 2); 785 | in if fillLen > 0 786 | then justifyRight (strLen + leftLen + rightLen) fill (justifyLeft (strLen + leftLen) fill str) 787 | else throw "std.string.justifyCenter: empty padding string"; 788 | 789 | 790 | /* camelTo :: char -> string -> string 791 | 792 | Converts from CamelCase to another lower case, interspersing 793 | the character between all capital letters and their previous 794 | entries. 795 | 796 | > string.camelTo "_" "CamelAPICase" 797 | "camel_api_case" 798 | > string.camelTo "-" "BlahFooBlah" 799 | "blah-foo-blah" 800 | > string.camelTo "@" "blahblahblah" 801 | "blahblahblah" 802 | > string.camelTo "!" "blahblahBlah" 803 | "blahblah!blah" 804 | */ 805 | camelTo = sep: s0: 806 | let 807 | str = toChars s0; 808 | go1 = s: 809 | if list.empty s 810 | then [] 811 | else if list.length s >= 3 812 | then 813 | let 814 | x = list.unsafeIndex s 0; 815 | u = list.unsafeIndex s 1; 816 | l = list.unsafeIndex s 2; 817 | xs = list.drop 3 s; 818 | in if isUpper u && isLower l 819 | then list.cons x (list.cons sep (list.cons u (list.cons l (go1 xs)))) 820 | else list.cons x (go1 (list.drop 1 s)) 821 | else 822 | let 823 | x = list.unsafeIndex s 0; 824 | xs = list.drop 1 s; 825 | in list.cons x (go1 xs); 826 | go2 = s: 827 | if list.empty s 828 | then [] 829 | else if list.length s >= 2 830 | then 831 | let 832 | l = list.unsafeIndex s 0; 833 | u = list.unsafeIndex s 1; 834 | xs = list.drop 2 s; 835 | in if isLower l && isUpper u 836 | then list.cons l (list.cons sep (list.cons u (go2 xs))) 837 | else list.cons (list.unsafeIndex s 0) (go2 (list.drop 1 s)) 838 | else 839 | let 840 | x = list.unsafeIndex s 0; 841 | xs = list.drop 1 s; 842 | in list.cons x (go2 xs); 843 | in concat (list.map toLower (go2 (go1 str))); 844 | 845 | /* convert :: a -> optional string 846 | convert :: string | path | null | number | bool -> optional.just string 847 | 848 | Converts a value to a string following the same rules as `builtins.toString`, 849 | returning `optional.nothing` if the value cannot be converted. 850 | */ 851 | convert = let 852 | typecheck = { 853 | path = const true; 854 | string = const true; 855 | null = const true; 856 | int = const true; 857 | float = const true; 858 | bool = const true; 859 | list = list.all canConvert; 860 | set = compose _optional.isJust coerce; 861 | }; 862 | canConvert = x: typecheck.${builtins.typeOf x} or (const false) x; 863 | in x: bool.toOptional (canConvert x) (unsafeConvert x); 864 | 865 | /* @partial 866 | unsafeConvert :: a -> string 867 | */ 868 | unsafeConvert = toString; 869 | 870 | /* coerce :: a -> optional string 871 | coerce :: string | path -> optional.just string 872 | 873 | Coerces a value to a string, or returns `optional.nothing` 874 | if the value cannot be converted. 875 | Besides strings and paths, only sets with either 876 | `outPath` or `__toString` keys can be used. 877 | */ 878 | coerce = let 879 | typecheck = { 880 | path = const true; 881 | string = const true; 882 | set = x: x ? outPath || x ? __toString; 883 | }; 884 | canCoerce = x: typecheck.${builtins.typeOf x} or (const false) x; 885 | in x: bool.toOptional (canCoerce x) (unsafeCoerce x); 886 | 887 | /* @partial 888 | unsafeCoerce :: a -> string 889 | */ 890 | unsafeCoerce = builtins.substring 0 (-1); 891 | } 892 | -------------------------------------------------------------------------------- /test/default.nix: -------------------------------------------------------------------------------- 1 | { system ? builtins.currentSystem 2 | }: 3 | 4 | with { std = import ./../default.nix; }; 5 | with std; 6 | 7 | with { sections = import ./sections/default.nix; }; 8 | 9 | builtins.deepSeq std builtins.derivation { 10 | name = "nix-std-test-${import ./../version.nix}"; 11 | inherit system; 12 | builder = "/bin/sh"; 13 | args = [ 14 | "-c" 15 | (string.unlines (builtins.attrValues sections) + '' 16 | echo > "$out" 17 | '') 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /test/framework.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../default.nix; }; 2 | with std; 3 | 4 | rec { 5 | section = module: tests: '' 6 | ( 7 | echo "''${SECTION_INDENT:-}testing ${module}" 8 | SECTION_INDENT="''${SECTION_INDENT:-} " 9 | ${string.concat 10 | (list.map 11 | (test: ''echo "''${SECTION_INDENT}...${test._0}"...; ${test._1}'') 12 | (set.toList tests)) 13 | } 14 | ) || exit $? 15 | ''; 16 | 17 | assertEqual = x: y: 18 | if x == y 19 | then string.empty 20 | else '' 21 | ERR=" 22 | assertEqual failed: x != y, where 23 | 24 | x = ${string.escape [''"''] (types.show x)} 25 | y = ${string.escape [''"''] (types.show y)} 26 | 27 | " 28 | printf "$ERR" 29 | exit 1 30 | ''; 31 | 32 | lawCheck = { lawName, typeName ? null }: x: y: 33 | if x == y 34 | then '' 35 | printf " ''${SECTION_INDENT:-}[${typeName}] ${lawName}: ✓" 36 | echo "" 37 | '' 38 | else '' 39 | ERR=" 40 | law does not hold: x != y, where 41 | 42 | x = ${string.escape [''"''] (types.show x)} 43 | y = ${string.escape [''"''] (types.show y)} 44 | 45 | " 46 | printf " ''${SECTION_INDENT:-}[${typeName}] ${lawName}: ✗" 47 | printf "$ERR" 48 | exit 1 49 | ''; 50 | 51 | functor = functor: 52 | { typeName 53 | , identity 54 | , composition 55 | }: 56 | let functorIdentity = xs: 57 | lawCheck { 58 | lawName = "functor identity"; 59 | inherit typeName; 60 | } (functor.map id xs) xs; 61 | functorComposition = f: g: xs: 62 | lawCheck { 63 | lawName = "functor composition"; 64 | inherit typeName; 65 | } (functor.map (compose f g) xs) 66 | (functor.map f (functor.map g xs)); 67 | in string.unlines [ 68 | (functorIdentity identity.x) 69 | (functorComposition composition.f composition.g composition.x) 70 | ]; 71 | 72 | applicative = applicative: 73 | { typeName 74 | , identity 75 | , composition 76 | , homomorphism 77 | , interchange 78 | }: 79 | let applicativeIdentity = v: 80 | lawCheck { 81 | lawName = "applicative identity"; 82 | inherit typeName; 83 | } (applicative.ap (applicative.pure id) v) v; 84 | 85 | applicativeComposition = u: v: w: 86 | lawCheck { 87 | lawName = "applicative composition"; 88 | inherit typeName; 89 | } (applicative.ap (applicative.ap ((applicative.ap (applicative.pure compose) u)) v) w) 90 | (applicative.ap u (applicative.ap v w)); 91 | 92 | applicativeHomomorphism = f: x: 93 | lawCheck { 94 | lawName = "applicative homomorphism"; 95 | inherit typeName; 96 | } (applicative.ap (applicative.pure f) (applicative.pure x)) 97 | (applicative.pure (f x)); 98 | 99 | applicativeInterchange = u: y: 100 | lawCheck { 101 | lawName = "applicative interchange"; 102 | inherit typeName; 103 | } (applicative.ap u (applicative.pure y)) 104 | (applicative.ap (applicative.pure (f: f y)) u); 105 | in string.unlines [ 106 | (applicativeIdentity identity.v) 107 | (applicativeComposition composition.u composition.v composition.w) 108 | (applicativeHomomorphism homomorphism.f homomorphism.x) 109 | (applicativeInterchange interchange.u interchange.y) 110 | ]; 111 | 112 | monad = monad: 113 | { typeName 114 | , leftIdentity 115 | , rightIdentity 116 | , associativity 117 | }: 118 | let monadLeftIdentity = f: x: 119 | lawCheck { 120 | lawName = "monad left identity"; 121 | inherit typeName; 122 | } (monad.bind (monad.pure x) f) (f x); 123 | 124 | monadRightIdentity = x: 125 | lawCheck { 126 | lawName = "monad right identity"; 127 | inherit typeName; 128 | } (monad.bind x monad.pure) x; 129 | 130 | monadAssociativity = m: f: g: 131 | lawCheck { 132 | lawName = "monad associativity"; 133 | inherit typeName; 134 | } (monad.bind (monad.bind m f) g) 135 | (monad.bind m (x: monad.bind (f x) g)); 136 | in string.unlines [ 137 | (monadLeftIdentity leftIdentity.f leftIdentity.x) 138 | (monadRightIdentity rightIdentity.x) 139 | (monadAssociativity associativity.m associativity.f associativity.g) 140 | ]; 141 | 142 | semigroup = semigroup: { typeName, associativity }: 143 | let semigroupAssociativity = a: b: c: 144 | lawCheck { 145 | lawName = "semigroup associativity"; 146 | inherit typeName; 147 | } (semigroup.append a (semigroup.append b c)) 148 | (semigroup.append (semigroup.append a b) c); 149 | in semigroupAssociativity associativity.a associativity.b associativity.c; 150 | 151 | monoid = monoid: { typeName, leftIdentity, rightIdentity }: 152 | let monoidLeftIdentity = x: 153 | lawCheck { 154 | lawName = "monoid left identity"; 155 | inherit typeName; 156 | } (monoid.append monoid.empty x) x; 157 | monoidRightIdentity = x: 158 | lawCheck { 159 | lawName = "monoid right identity"; 160 | inherit typeName; 161 | } (monoid.append x monoid.empty) x; 162 | in string.unlines [ 163 | (monoidLeftIdentity leftIdentity.x) 164 | (monoidRightIdentity rightIdentity.x) 165 | ]; 166 | } 167 | -------------------------------------------------------------------------------- /test/sections/bits.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | section "std.num.bits" { 7 | bitSize = string.unlines [ 8 | (assertEqual num.maxInt (num.pow 2 (num.bits.bitSize - 1) - 1)) 9 | (assertEqual num.minInt (- num.pow 2 (num.bits.bitSize - 1))) 10 | ]; 11 | bitAnd = string.unlines [ 12 | (assertEqual (num.bits.bitAnd 5 3) 1) 13 | (assertEqual (num.bits.bitAnd (-1) 6148914691236517205) 6148914691236517205) 14 | (assertEqual (num.bits.bitAnd (-6148914691236517206) 6148914691236517205) 0) 15 | ]; 16 | bitOr = string.unlines [ 17 | (assertEqual (num.bits.bitOr 5 3) 7) 18 | (assertEqual (num.bits.bitOr (-1) 6148914691236517205) (-1)) 19 | (assertEqual (num.bits.bitOr (-6148914691236517206) 6148914691236517205) (-1)) 20 | ]; 21 | bitXor = string.unlines [ 22 | (assertEqual (num.bits.bitXor 5 3) 6) 23 | (assertEqual (num.bits.bitXor (-1) 6148914691236517205) (-6148914691236517206)) 24 | (assertEqual (num.bits.bitXor (-6148914691236517206) 6148914691236517205) (-1)) 25 | ]; 26 | bitNot = string.unlines [ 27 | (assertEqual (num.bits.bitNot 0) (-1)) 28 | (assertEqual (num.bits.bitNot (-1)) 0) 29 | (assertEqual (num.bits.bitNot (-6148914691236517206)) 6148914691236517205) 30 | ]; 31 | bit = 32 | let 33 | case = n: 34 | let 35 | # Manually compute 2^n 36 | go = acc: m: 37 | if m == 0 38 | then acc 39 | else let r = 2 * acc; in builtins.seq r (go r (m - 1)); 40 | in assertEqual (num.bits.bit n) (go 1 n); 41 | in string.unlines (list.map case (list.range 0 (num.bits.bitSize - 1))); 42 | set = 43 | let 44 | case = n: string.unlines [ 45 | # Sets cleared bit 46 | (assertEqual (num.bits.set 0 n) (num.bits.bit n)) 47 | # Idempotence on set bit 48 | (assertEqual (num.bits.set (num.bits.set 0 n) n) (num.bits.set 0 n)) 49 | ]; 50 | in string.unlines (list.map case (list.range 0 (num.bits.bitSize - 1))); 51 | clear = 52 | let 53 | case = n: string.unlines [ 54 | # Clears set bit 55 | (assertEqual (num.bits.clear (-1) n) (num.bits.bitNot (num.bits.bit n))) 56 | # Idempotence on cleared bit 57 | (assertEqual (num.bits.clear (-1) n) (num.bits.clear (num.bits.clear (-1) n) n)) 58 | ]; 59 | in string.unlines (list.map case (list.range 0 (num.bits.bitSize - 1))); 60 | toggle = 61 | let 62 | case = n: string.unlines [ 63 | # Clears set bit 64 | (assertEqual (num.bits.toggle (-1) n) (num.bits.bitNot (num.bits.bit n))) 65 | # Sets cleared bit 66 | (assertEqual (num.bits.toggle 0 n) (num.bits.bit n)) 67 | ]; 68 | in string.unlines (list.map case (list.range 0 (num.bits.bitSize - 1))); 69 | test = 70 | let 71 | case = n: string.unlines [ 72 | # Set bit 73 | (assertEqual (num.bits.test (num.bits.bit n) n) true) 74 | # Cleared bit 75 | (assertEqual (num.bits.test 0 n) false) 76 | ]; 77 | in string.unlines (list.map case (list.range 0 (num.bits.bitSize - 1))); 78 | shiftL = string.unlines [ 79 | (assertEqual (num.bits.shiftL 5 3) 40) 80 | (assertEqual (num.bits.shiftL 0 5) 0) 81 | (assertEqual (num.bits.shiftL (-1) 3) (-8)) 82 | (assertEqual (num.bits.shiftL (-1) 0) (-1)) 83 | (assertEqual (num.bits.shiftL (-1) num.bits.bitSize) (0)) 84 | (assertEqual (num.bits.shiftL (num.bits.bit (num.bits.bitSize - 1)) 1) 0) 85 | (assertEqual (num.bits.shiftL (-1) (-1)) (-1)) 86 | (assertEqual (num.bits.shiftL (-1) (-num.bits.bitSize)) (-1)) 87 | ]; 88 | shiftLU = string.unlines [ 89 | (assertEqual (num.bits.shiftLU 5 3) 40) 90 | (assertEqual (num.bits.shiftLU 0 5) 0) 91 | (assertEqual (num.bits.shiftLU (-1) 3) (-8)) 92 | (assertEqual (num.bits.shiftLU (-1) 0) (-1)) 93 | (assertEqual (num.bits.shiftLU (-1) num.bits.bitSize) (0)) 94 | (assertEqual (num.bits.shiftLU (num.bits.bit (num.bits.bitSize - 1)) 1) 0) 95 | (assertEqual (num.bits.shiftLU (-1) (-1)) num.maxInt) 96 | (assertEqual (num.bits.shiftLU (-1) (-num.bits.bitSize)) 0) 97 | ]; 98 | shiftR = string.unlines [ 99 | (assertEqual (num.bits.shiftR 5 3) 0) 100 | (assertEqual (num.bits.shiftR 0 5) 0) 101 | (assertEqual (num.bits.shiftR (-30) 2) (-8)) 102 | (assertEqual (num.bits.shiftR (-1) 3) (-1)) 103 | (assertEqual (num.bits.shiftR (-1) 0) (-1)) 104 | (assertEqual (num.bits.shiftR (-1) num.bits.bitSize) (-1)) 105 | (assertEqual (num.bits.shiftR (-1) (-1)) (-2)) 106 | (assertEqual (num.bits.shiftR (-1) (-num.bits.bitSize)) 0) 107 | ]; 108 | shiftRU = string.unlines [ 109 | (assertEqual (num.bits.shiftRU 5 3) 0) 110 | (assertEqual (num.bits.shiftRU 0 5) 0) 111 | (assertEqual (num.bits.shiftRU (-30) 2) 4611686018427387896) 112 | (assertEqual (num.bits.shiftRU (-1) 3) 2305843009213693951) 113 | (assertEqual (num.bits.shiftRU (-1) 0) (-1)) 114 | (assertEqual (num.bits.shiftRU (-1) num.bits.bitSize) 0) 115 | (assertEqual (num.bits.shiftRU (-1) (-1)) (-2)) 116 | (assertEqual (num.bits.shiftRU (-1) (-num.bits.bitSize)) 0) 117 | ]; 118 | rotateL = string.unlines [ 119 | (assertEqual (num.bits.rotateL 5 3) 40) 120 | (assertEqual (num.bits.rotateL 0 5) 0) 121 | (assertEqual (num.bits.rotateL (-1) 3) (-1)) 122 | (assertEqual (num.bits.rotateL (-1) 0) (-1)) 123 | (assertEqual (num.bits.rotateL (-1) (num.bits.bitSize + 5)) (-1)) 124 | (assertEqual (num.bits.rotateL 15 num.bits.bitSize) 15) 125 | (assertEqual (num.bits.rotateL (num.bits.bit (num.bits.bitSize - 1)) 1) 1) 126 | (assertEqual (num.bits.rotateL (-1) (-1)) (-1)) 127 | (assertEqual (num.bits.rotateL 15 (-2)) (-4611686018427387901)) 128 | (assertEqual (num.bits.rotateL 15 (-num.bits.bitSize)) 15) 129 | ]; 130 | rotateR = string.unlines [ 131 | (assertEqual (num.bits.rotateR 25 3) 2305843009213693955) 132 | (assertEqual (num.bits.rotateR 0 5) 0) 133 | (assertEqual (num.bits.rotateR (-1) 3) (-1)) 134 | (assertEqual (num.bits.rotateR (-1) 0) (-1)) 135 | (assertEqual (num.bits.rotateR (-1) (num.bits.bitSize + 5)) (-1)) 136 | (assertEqual (num.bits.rotateR 15 num.bits.bitSize) 15) 137 | (assertEqual (num.bits.rotateR 1 1) (num.bits.bit (num.bits.bitSize - 1))) 138 | (assertEqual (num.bits.rotateR (-1) (-1)) (-1)) 139 | (assertEqual (num.bits.rotateR 15 (-2)) 60) 140 | (assertEqual (num.bits.rotateR 15 (-num.bits.bitSize)) 15) 141 | ]; 142 | popCount = string.unlines [ 143 | (assertEqual (num.bits.popCount 0) 0) 144 | (assertEqual (num.bits.popCount (-1)) num.bits.bitSize) 145 | (assertEqual (num.bits.popCount 6148914691236517205) (num.bits.bitSize / 2)) 146 | (assertEqual (num.bits.popCount (-6148914691236517206)) (num.bits.bitSize / 2)) 147 | (assertEqual (num.bits.popCount 3689348814741910323) (num.bits.bitSize / 2)) 148 | ]; 149 | bitScanForward = 150 | let 151 | case = n: assertEqual (num.bits.bitScanForward (num.bits.bit n)) n; 152 | singleBitTests = string.unlines (list.map case (list.range 0 num.bits.bitSize)); 153 | in string.unlines [ 154 | singleBitTests 155 | (assertEqual (num.bits.bitScanForward 5) 0) 156 | (assertEqual (num.bits.bitScanForward (num.bits.bitOr (num.bits.bit (num.bits.bitSize - 1)) 1)) 0) 157 | (assertEqual (num.bits.bitScanForward (-1)) 0) 158 | ]; 159 | countTrailingZeros = 160 | let 161 | case = n: assertEqual (num.bits.countTrailingZeros (num.bits.bit n)) n; 162 | in string.unlines (list.map case (list.range 0 num.bits.bitSize)); 163 | bitScanReverse = 164 | let 165 | case = n: assertEqual (num.bits.bitScanReverse (num.bits.bit n)) n; 166 | singleBitTests = string.unlines (list.map case (list.range 0 num.bits.bitSize)); 167 | in string.unlines [ 168 | singleBitTests 169 | (assertEqual (num.bits.bitScanReverse 5) 2) 170 | (assertEqual (num.bits.bitScanReverse (num.bits.bitOr (num.bits.bit (num.bits.bitSize - 1)) 1)) (num.bits.bitSize - 1)) 171 | (assertEqual (num.bits.bitScanReverse (-1)) (num.bits.bitSize - 1)) 172 | ]; 173 | countLeadingZeros = 174 | let 175 | case = n: 176 | assertEqual 177 | (num.bits.countLeadingZeros (num.bits.bit n)) 178 | (if n == 64 then 64 else num.bits.bitSize - n - 1); 179 | in string.unlines (list.map case (list.range 0 num.bits.bitSize)); 180 | } 181 | -------------------------------------------------------------------------------- /test/sections/bool.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | section "std.bool" { 7 | true = assertEqual builtins.true bool.true; 8 | false = assertEqual builtins.false bool.false; 9 | not = string.unlines [ 10 | (assertEqual (bool.not false) true) 11 | (assertEqual (bool.not true) false) 12 | ]; 13 | ifThenElse = string.unlines [ 14 | (assertEqual (ifThenElse true "left" "right") "left") 15 | (assertEqual (ifThenElse false "left" "right") "right") 16 | ]; 17 | toOptional = string.unlines [ 18 | (assertEqual (optional.just 0) (bool.toOptional true 0)) 19 | (assertEqual optional.nothing (bool.toOptional false 0)) 20 | ]; 21 | toNullable = string.unlines [ 22 | (assertEqual 0 (bool.toNullable true 0)) 23 | (assertEqual null (bool.toNullable false 0)) 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /test/sections/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | bool = import ./bool.nix; 3 | bits = import ./bits.nix; 4 | fixpoints = import ./fixpoints.nix; 5 | function = import ./function.nix; 6 | list = import ./list.nix; 7 | nonempty = import ./nonempty.nix; 8 | num = import ./num.nix; 9 | optional = import ./optional.nix; 10 | path = import ./path.nix; 11 | regex = import ./regex.nix; 12 | serde = import ./serde.nix; 13 | set = import ./set.nix; 14 | string = import ./string.nix; 15 | } 16 | -------------------------------------------------------------------------------- /test/sections/fixpoints.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | section "std.fixpoints" { 7 | fix = string.unlines [ 8 | (assertEqual 0 (fixpoints.fix (const 0))) 9 | (assertEqual 120 (fixpoints.fix (r: n: if n == 0 then 1 else builtins.mul n (r (n - 1))) 5)) 10 | ]; 11 | until = assertEqual 400 (fixpoints.until (x: num.mod x 20 == 0) (compose (builtins.add 1) (builtins.mul 7)) 1); 12 | } 13 | -------------------------------------------------------------------------------- /test/sections/function.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | let 7 | testFn = { a, b, c ? 1 }: a + b + c; 8 | testFnSet = { 9 | __functor = _: testFn; 10 | foo = "bar"; 11 | }; 12 | testArgs = { a = 0; b = 1; }; 13 | in section "std.function" { 14 | callable = string.unlines [ 15 | (assertEqual 2 (testFn testArgs)) 16 | (assertEqual 2 (testFnSet testArgs)) 17 | ]; 18 | check = string.unlines [ 19 | (assertEqual true (types.function.check testFn)) 20 | (assertEqual true (types.lambda.check testFn)) 21 | (assertEqual false (types.functionSet.check testFn)) 22 | (assertEqual true (types.function.check testFnSet)) 23 | (assertEqual true (types.functionSet.check testFnSet)) 24 | (assertEqual false (types.lambda.check testFnSet)) 25 | ]; 26 | show = string.unlines [ 27 | (assertEqual "«lambda»" (types.function.show function.id)) 28 | (assertEqual "{ a, b, c ? «code» }: «code»" (types.function.show testFn)) 29 | ]; 30 | args = string.unlines [ 31 | (assertEqual { a = false; b = false; c = true; } (function.args testFn)) 32 | (assertEqual { a = false; b = false; c = true; } (function.args testFnSet)) 33 | ]; 34 | setArgs = assertEqual { a = false; b = false; } (function.args ( 35 | function.setArgs (set.without ["c"] (function.args testFn)) testFn 36 | )); 37 | toFunctionSet = string.unlines [ 38 | (assertEqual true ((function.toSet testFnSet) ? foo)) 39 | (assertEqual 2 (function.toSet testFn testArgs)) 40 | (assertEqual 2 (function.toSet testFnSet testArgs)) 41 | (assertEqual true (types.functionSet.check (function.toSet testFn))) 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /test/sections/list.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | with { inherit (std.tuple) tuple2; }; 4 | 5 | with (import ./../framework.nix); 6 | 7 | section "std.list" { 8 | check = string.unlines [ 9 | (assertEqual true (types.list.check [ ])) 10 | (assertEqual false (types.list.check { })) 11 | (assertEqual true ((types.listOf types.int).check [ ])) 12 | (assertEqual true ((types.listOf types.int).check [ 1 2 3 ])) 13 | (assertEqual false ((types.listOf types.int).check [ 1 2 "foo" ])) 14 | ]; 15 | checkNonEmpty = string.unlines [ 16 | (assertEqual true (types.nonEmptyList.check [ 1 2 3 ])) 17 | (assertEqual false (types.nonEmptyList.check [ ])) 18 | (assertEqual false (types.nonEmptyList.check { })) 19 | (assertEqual true ((types.nonEmptyListOf types.int).check [ 1 2 3 ])) 20 | (assertEqual false ((types.nonEmptyListOf types.int).check [ 1 2 "foo" ])) 21 | ]; 22 | 23 | show = string.unlines [ 24 | (assertEqual "[ ]" (types.list.show [ ])) 25 | (assertEqual "[ 1, 2, 3 ]" (types.list.show [ 1 2 3 ])) 26 | ]; 27 | 28 | laws = string.unlines [ 29 | (functor list.functor { 30 | typeName = "list"; 31 | identity = { 32 | x = [1 2 3 4 5]; 33 | }; 34 | composition = { 35 | f = x: x ++ x; 36 | g = list.singleton; 37 | x = [1 2 3 4 5]; 38 | }; 39 | }) 40 | (applicative list.applicative { 41 | typeName = "list"; 42 | identity = { 43 | v = [1 2 3 4]; 44 | }; 45 | composition = { 46 | u = [ 47 | (b: builtins.toString (b + 1)) 48 | (b: builtins.toString (b * 2)) 49 | (b: builtins.toString (5 * (b + 1))) 50 | ]; 51 | v = [ 52 | (a: a + 1) 53 | (a: a * 2) 54 | (b: 5 * (b + 1)) 55 | ]; 56 | w = [ 1 2 3 4 5]; 57 | }; 58 | homomorphism = { 59 | f = builtins.toString; 60 | x = 5; 61 | }; 62 | interchange = { 63 | u = list.ifor 64 | ["foo" "bar" "baz"] 65 | (i: s: (u: builtins.toString u + "-" + s + "-" + builtins.toString i)); 66 | y = 20.0; 67 | }; 68 | }) 69 | (monad list.monad { 70 | typeName = "list"; 71 | leftIdentity = { 72 | f = x: [x x x]; 73 | x = 10; 74 | }; 75 | rightIdentity = { 76 | x = list.range 1 10; 77 | }; 78 | associativity = { 79 | m = [1 2 3 4 5]; 80 | f = x: list.singleton (x + 1); 81 | g = x: list.range x (x + 1); 82 | }; 83 | }) 84 | (semigroup list.semigroup { 85 | typeName = "list"; 86 | associativity = { 87 | a = [1 2]; 88 | b = ["foo" "bar"]; 89 | c = [true false]; 90 | }; 91 | }) 92 | (monoid list.monoid { 93 | typeName = "list"; 94 | leftIdentity = { 95 | x = [1 2]; 96 | }; 97 | rightIdentity = { 98 | x = [1 2]; 99 | }; 100 | }) 101 | ]; 102 | 103 | match = 104 | let ls = ["foo" "baz" "bow" "bar" "bed"]; 105 | go = xs0: list.match xs0 { 106 | nil = throw "std.list.match test reached end of list"; 107 | cons = x: xs: 108 | if x == "bar" 109 | then x 110 | else go xs; 111 | }; 112 | in assertEqual "bar" (go ls); 113 | 114 | empty = string.unlines [ 115 | (assertEqual true (list.empty [])) 116 | (assertEqual false (list.empty [null])) 117 | ]; 118 | 119 | unsafeHead = assertEqual 10 (list.unsafeHead [10 20 30]); 120 | unsafeTail = assertEqual [20 30] (list.unsafeTail [10 20 30]); 121 | unsafeInit = assertEqual [10 20] (list.unsafeInit [10 20 30]); 122 | unsafeLast = assertEqual 30 (list.unsafeLast [10 20 30]); 123 | 124 | head = string.unlines [ 125 | (assertEqual (optional.just 10) (list.head [10 20 30])) 126 | (assertEqual optional.nothing (list.head [])) 127 | ]; 128 | tail = string.unlines [ 129 | (assertEqual (optional.just [20 30]) (list.tail [10 20 30])) 130 | (assertEqual optional.nothing (list.tail [])) 131 | ]; 132 | init = string.unlines [ 133 | (assertEqual (optional.just [10 20]) (list.init [10 20 30])) 134 | (assertEqual optional.nothing (list.init [])) 135 | ]; 136 | last = string.unlines [ 137 | (assertEqual (optional.just 30) (list.last [10 20 30])) 138 | (assertEqual optional.nothing (list.last [])) 139 | ]; 140 | 141 | take = let xs = list.range 1 20; in string.unlines [ 142 | (assertEqual [1 2 3 4] (list.take 4 xs)) 143 | (assertEqual xs (list.take 100 xs)) 144 | ]; 145 | 146 | drop = let xs = list.range 1 20; in string.unlines [ 147 | (assertEqual (list.range 5 20) (list.drop 4 xs)) 148 | (assertEqual [] (list.drop 100 xs)) 149 | (assertEqual xs (list.drop (-1) xs)) 150 | ]; 151 | 152 | takeEnd = let xs = list.range 1 20; in string.unlines [ 153 | (assertEqual [17 18 19 20] (list.takeEnd 4 xs)) 154 | (assertEqual xs (list.takeEnd 100 xs)) 155 | (assertEqual [] (list.takeEnd (-1) xs)) 156 | ]; 157 | 158 | dropEnd = let xs = list.range 1 20; in string.unlines [ 159 | (assertEqual (list.range 1 16) (list.dropEnd 4 xs)) 160 | (assertEqual [] (list.dropEnd 100 xs)) 161 | (assertEqual xs (list.dropEnd (-1) xs)) 162 | ]; 163 | length = assertEqual 20 (list.length (list.range 1 20)); 164 | singleton = assertEqual [10] (list.singleton 10); 165 | map = assertEqual ["foo-0" "foo-1"] (list.map (i: "foo-" + builtins.toString i) [0 1]); 166 | for = assertEqual ["foo-0" "foo-1"] (list.for [0 1] (i: "foo-" + builtins.toString i)); 167 | imap = assertEqual ["foo-0" "bar-1"] (list.imap (i: s: s + "-" + builtins.toString i) ["foo" "bar"]); 168 | modifyAt = string.unlines [ 169 | (assertEqual [ 1 20 3 ] (list.modifyAt 1 (x: 10 * x) [ 1 2 3 ])) 170 | (assertEqual [ 1 2 3 ] (list.modifyAt (-3) (x: 10 * x) [ 1 2 3 ])) 171 | ]; 172 | setAt = string.unlines [ 173 | (assertEqual [ 1 20 3 ] (list.setAt 1 20 [ 1 2 3 ])) 174 | (assertEqual [ 1 2 3 ] (list.setAt (-3) 20 [ 1 2 3 ])) 175 | ]; 176 | insertAt = string.unlines [ 177 | (assertEqual [ 1 20 2 3 ] (list.insertAt 1 20 [ 1 2 3 ])) 178 | (assertEqual [ 20 1 2 3 ] (list.insertAt 0 20 [ 1 2 3 ])) 179 | (assertEqual [ 1 2 3 20 ] (list.insertAt 3 20 [ 1 2 3 ])) 180 | ]; 181 | ifor = assertEqual ["foo-0" "bar-1"] (list.ifor ["foo" "bar"] (i: s: s + "-" + builtins.toString i)); 182 | unsafeIndex = assertEqual "barry" (list.unsafeIndex ["bar" "ry" "barry"] 2); 183 | index = string.unlines [ 184 | (assertEqual (optional.just "barry") (list.index ["bar" "ry" "barry"] 2)) 185 | (assertEqual optional.nothing (list.index ["bar" "ry" "barry"] (-1))) 186 | (assertEqual optional.nothing (list.index ["bar" "ry" "barry"] 3)) 187 | ]; 188 | concat = assertEqual ["foo" "bar" "baz" "quux"] (list.concat [["foo"] ["bar"] ["baz" "quux"]]); 189 | filter = assertEqual ["foo" "fun" "friends"] (list.filter (string.hasPrefix "f") ["foo" "oof" "fun" "nuf" "friends" "sdneirf"]); 190 | elem = assertEqual builtins.true (list.elem "friend" ["texas" "friend" "amigo"]); 191 | notElem = assertEqual builtins.true (list.notElem "foo" ["texas" "friend" "amigo"]); 192 | generate = string.unlines [ 193 | (assertEqual (list.range 0 4) (list.generate id 5)) 194 | ]; 195 | nil = assertEqual [] list.nil; 196 | cons = assertEqual [1 2 3 4 5] (list.cons 1 [2 3 4 5]); 197 | uncons = string.unlines [ 198 | (assertEqual optional.nothing (list.uncons [])) 199 | (assertEqual (optional.just (tuple2 1 [2 3])) (list.uncons [1 2 3])) 200 | ]; 201 | snoc = assertEqual [1 2 3 4 5] (list.snoc [1 2 3 4] 5); 202 | 203 | foldr = assertEqual 55 (list.foldr builtins.add 0 (list.range 1 10)); 204 | foldl' = assertEqual 3628800 (list.foldl' builtins.mul 1 (list.range 1 10)); 205 | foldMap = string.unlines [ 206 | (assertEqual (optional.just 1) (list.foldMap std.monoid.first optional.just (list.range 1 10))) 207 | (assertEqual 321 ((list.foldMap std.monoid.endo id [ (x: builtins.mul x 3) (x: builtins.add x 7) (x: num.pow x 2) ]) 10)) 208 | ]; 209 | fold = string.unlines [ 210 | (assertEqual (optional.just 1) (list.fold std.monoid.first (list.map optional.just (list.range 1 10)))) 211 | (assertEqual 321 ((list.fold std.monoid.endo [ (x: builtins.mul x 3) (x: builtins.add x 7) (x: num.pow x 2) ]) 10)) 212 | ]; 213 | sum = assertEqual 55 (list.sum (list.range 1 10)); 214 | product = assertEqual 3628800 (list.product (list.range 1 10)); 215 | concatMap = assertEqual (list.replicate 3 "foo" ++ list.replicate 3 "bar" ++ list.replicate 3 "baz") (list.concatMap (s: [s s s]) ["foo" "bar" "baz"]); 216 | any = string.unlines [ 217 | (assertEqual true (list.any num.even [1 2 3 4 5])) 218 | (assertEqual false (list.any num.even [1 3 5])) 219 | (assertEqual false (list.any (const true) [])) 220 | (assertEqual false (list.any (const false) [])) 221 | ]; 222 | all = string.unlines [ 223 | (assertEqual true (list.all num.even (list.generate (i: builtins.mul i 2) 10))) 224 | (assertEqual false (list.all num.even [2 4 5 8])) 225 | (assertEqual true (list.all (const true) [])) 226 | (assertEqual true (list.all (const false) [])) 227 | ]; 228 | none = string.unlines [ 229 | (assertEqual true (list.none num.odd (list.generate (i: builtins.mul i 2) 10))) 230 | (assertEqual false (list.none num.odd [2 4 5 8])) 231 | ]; 232 | count = assertEqual 11 (list.count num.even (list.generate id 21)); 233 | optional = string.unlines [ 234 | (assertEqual [] (list.optional false null)) 235 | (assertEqual ["foo"] (list.optional true "foo")) 236 | ]; 237 | optionals = string.unlines [ 238 | (assertEqual [] (list.optionals false null)) 239 | (assertEqual [1 2 3] (list.optionals true [1 2 3])) 240 | ]; 241 | replicate = assertEqual [1 1 1 1 1 1] (list.replicate 6 1); 242 | slice = string.unlines [ 243 | (assertEqual [3 4] (list.slice 2 2 [ 1 2 3 4 5 ])) 244 | (assertEqual [3 4 5] (list.slice 2 30 [ 1 2 3 4 5 ])) 245 | (assertEqual [2 3 4 5] (list.slice 1 null [ 1 2 3 4 5 ])) 246 | ]; 247 | range = assertEqual [1 2 3 4 5] (list.range 1 5); 248 | partition = string.unlines [ 249 | (assertEqual (tuple2 [1 3 5 7] [0 2 4 6 8]) (list.partition num.odd (list.range 0 8))) 250 | (assertEqual (tuple2 ["foo" "fum"] ["haha"]) (list.partition (string.hasPrefix "f") ["foo" "fum" "haha"])) 251 | ]; 252 | zipWith = assertEqual 253 | ["foo-0" "foo-1" "foo-2"] 254 | (list.zipWith (s: i: s + "-" + builtins.toString i) (list.replicate 10 "foo") (list.range 0 2)); 255 | zip = assertEqual 256 | [(tuple2 "foo" 0) (tuple2 "foo" 1) (tuple2 "foo" 2)] 257 | (list.zip (list.replicate 10 "foo") (list.range 0 2)); 258 | sequence = string.unlines [ 259 | (let ls = list.range 1 10; in assertEqual ls (list.sequence nullable.applicative ls)) 260 | (let ls = list.range 1 10; in assertEqual null (list.sequence nullable.applicative (ls ++ [null]))) 261 | ]; 262 | traverse = string.unlines [ 263 | (let ls = list.range 1 10; in assertEqual ls (list.traverse nullable.applicative (x: if (num.even x || num.odd x) then x else null) ls)) 264 | ]; 265 | reverse = string.unlines [ 266 | (assertEqual [3 2 1] (list.reverse [1 2 3])) 267 | (assertEqual [] (list.reverse [])) 268 | ]; 269 | 270 | unfold = assertEqual [10 9 8 7 6 5 4 3 2 1] 271 | (list.unfold (n: if n == 0 then optional.nothing else optional.just (tuple2 n (n - 1))) 10); 272 | 273 | findIndex = string.unlines [ 274 | (assertEqual (optional.just 1) (list.findIndex num.even [ 1 2 3 4 ])) 275 | (assertEqual optional.nothing (list.findIndex num.even [ 1 3 5 ])) 276 | ]; 277 | 278 | findLastIndex = string.unlines [ 279 | (assertEqual (optional.just 3) (list.findLastIndex num.even [ 1 2 3 4 ])) 280 | (assertEqual optional.nothing (list.findLastIndex num.even [ 1 3 5 ])) 281 | ]; 282 | 283 | find = string.unlines [ 284 | (assertEqual (optional.just 2) (list.find num.even [ 1 2 3 4 ])) 285 | (assertEqual optional.nothing (list.find num.even [ 1 3 5 ])) 286 | ]; 287 | 288 | findLast = string.unlines [ 289 | (assertEqual (optional.just 4) (list.findLast num.even [ 1 2 3 4 ])) 290 | (assertEqual optional.nothing (list.findLast num.even [ 1 3 5 ])) 291 | ]; 292 | 293 | splitAt = assertEqual (tuple2 [ 1 ] [ 2 3 ]) (list.splitAt 1 [ 1 2 3 ]); 294 | 295 | takeWhile = assertEqual [ 2 4 6 ] (list.takeWhile num.even [ 2 4 6 9 10 11 12 14 ]); 296 | 297 | dropWhile = assertEqual [ 9 10 11 12 14 ] (list.dropWhile num.even [ 2 4 6 9 10 11 12 14 ]); 298 | 299 | takeWhileEnd = assertEqual [ 12 14 ] (list.takeWhileEnd num.even [ 2 4 6 9 10 11 12 14 ]); 300 | 301 | dropWhileEnd = assertEqual [ 2 4 6 9 10 11 ] (list.dropWhileEnd num.even [ 2 4 6 9 10 11 12 14 ]); 302 | 303 | span = assertEqual (tuple2 [ 2 4 6 ] [ 9 10 11 12 14 ]) 304 | (list.span num.even [ 2 4 6 9 10 11 12 14 ]); 305 | 306 | break = assertEqual (tuple2 [ 2 4 6 ] [ 9 10 11 12 14 ]) 307 | (list.break num.odd [ 2 4 6 9 10 11 12 14 ]); 308 | } 309 | -------------------------------------------------------------------------------- /test/sections/nonempty.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | with { inherit (std.tuple) tuple2; }; 4 | 5 | with (import ./../framework.nix); 6 | 7 | section "std.nonempty" { 8 | check = string.unlines [ 9 | (assertEqual true (types.nonEmpty.check (nonempty.make 3 [2 1]))) 10 | (assertEqual false (types.nonEmpty.check (nonempty.make 3 "foo"))) 11 | (assertEqual false (types.nonEmpty.check [ 1 ])) 12 | (assertEqual false (types.nonEmpty.check (nonempty.make 3 [2 1] // { foo = "bar"; }))) 13 | (assertEqual true ((types.nonEmptyOf types.int).check (nonempty.make 3 [2 1]))) 14 | (assertEqual false ((types.nonEmptyOf types.int).check (nonempty.make 3 [2 "foo"]))) 15 | (assertEqual true ((types.nonEmptyListOf types.int).check (nonempty.toList (nonempty.make 3 [2 1])))) 16 | ]; 17 | 18 | show = string.unlines [ 19 | (assertEqual "nonempty [ 0 ]" (types.nonEmpty.show (nonempty.make 0 []))) 20 | (assertEqual "nonempty [ 0, 1 ]" (types.nonEmpty.show (nonempty.make 0 [1]))) 21 | (assertEqual "{ foo = nonempty [ 0, 1 ]; }" ((types.attrsOf types.nonEmpty).show { 22 | foo = nonempty.make 0 [1]; 23 | })) 24 | ]; 25 | 26 | laws = string.unlines [ 27 | (functor nonempty.functor { 28 | typeName = "nonempty"; 29 | identity = { 30 | x = nonempty.unsafeFromList [1 2 3 4 5]; 31 | }; 32 | composition = { 33 | f = x: nonempty.semigroup.append x x; 34 | g = nonempty.singleton; 35 | x = nonempty.unsafeFromList [1 2 3 4 5]; 36 | }; 37 | }) 38 | (applicative nonempty.applicative { 39 | typeName = "nonempty"; 40 | identity = { 41 | v = nonempty.unsafeFromList [1 2 3 4]; 42 | }; 43 | composition = { 44 | u = nonempty.unsafeFromList [ 45 | (b: builtins.toString (b + 1)) 46 | (b: builtins.toString (b * 2)) 47 | (b: builtins.toString (5 * (b + 1))) 48 | ]; 49 | v = nonempty.unsafeFromList [ 50 | (a: a + 1) 51 | (a: a * 2) 52 | (b: 5 * (b + 1)) 53 | ]; 54 | w = nonempty.unsafeFromList [ 1 2 3 4 5 ]; 55 | }; 56 | homomorphism = { 57 | f = builtins.toString; 58 | x = 5; 59 | }; 60 | interchange = { 61 | u = nonempty.ifor 62 | (nonempty.unsafeFromList ["foo" "bar" "baz"]) 63 | (i: s: (u: builtins.toString u + "-" + s + "-" + builtins.toString i)); 64 | y = 20.0; 65 | }; 66 | }) 67 | (monad nonempty.monad { 68 | typeName = "nonempty"; 69 | leftIdentity = { 70 | f = x: nonempty.make x [x x]; 71 | x = 10; 72 | }; 73 | rightIdentity = { 74 | x = nonempty.unsafeFromList (list.range 1 10); 75 | }; 76 | associativity = { 77 | m = nonempty.unsafeFromList [1 2 3 4 5]; 78 | f = x: nonempty.singleton (x + 1); 79 | g = x: nonempty.unsafeFromList (list.range x (x + 1)); 80 | }; 81 | }) 82 | (semigroup nonempty.semigroup { 83 | typeName = "nonempty"; 84 | associativity = { 85 | a = nonempty.unsafeFromList [1 2]; 86 | b = nonempty.unsafeFromList ["foo" "bar"]; 87 | c = nonempty.unsafeFromList [true false]; 88 | }; 89 | }) 90 | ]; 91 | 92 | match = 93 | let ls = nonempty.unsafeFromList ["foo" "baz" "bow" "bar" "bed"]; 94 | go = xs0: nonempty.match xs0 { 95 | cons = _: xs: builtins.head xs; 96 | }; 97 | in assertEqual "baz" (go ls); 98 | 99 | fromList = string.unlines [ 100 | (assertEqual optional.nothing (nonempty.fromList [])) 101 | (assertEqual (optional.just (nonempty.singleton 1)) (nonempty.fromList [1])) 102 | (assertEqual (optional.just (nonempty.make 1 [2])) (nonempty.fromList [1 2])) 103 | ]; 104 | 105 | unsafeFromList = string.unlines [ 106 | (assertEqual false ((builtins.tryEval (nonempty.unsafeFromList [])).success)) 107 | (assertEqual (nonempty.singleton 1) (nonempty.unsafeFromList [1])) 108 | (assertEqual (nonempty.make 1 [2]) (nonempty.unsafeFromList [1 2])) 109 | ]; 110 | 111 | toList = string.unlines [ 112 | (assertEqual [1] (nonempty.toList (nonempty.singleton 1))) 113 | (assertEqual [1 2] (nonempty.toList (nonempty.make 1 [2]))) 114 | ]; 115 | 116 | head = assertEqual 10 (nonempty.head (nonempty.make 10 [20 30])); 117 | tail = assertEqual [20 30] (nonempty.tail (nonempty.make 10 [20 30])); 118 | init = assertEqual [10 20] (nonempty.init (nonempty.make 10 [20 30])); 119 | last = assertEqual 30 (nonempty.last (nonempty.make 10 [20 30])); 120 | 121 | take = let xs = nonempty.unsafeFromList (list.range 1 20); in string.unlines [ 122 | (assertEqual [1 2 3 4] (nonempty.take 4 xs)) 123 | (assertEqual (nonempty.toList xs) (nonempty.take 100 xs)) 124 | ]; 125 | 126 | drop = let xs = nonempty.unsafeFromList (list.range 1 20); in string.unlines [ 127 | (assertEqual (list.range 5 20) (nonempty.drop 4 xs)) 128 | (assertEqual [] (nonempty.drop 100 xs)) 129 | (assertEqual (nonempty.toList xs) (nonempty.drop (-1) xs)) 130 | ]; 131 | 132 | takeEnd = let xs = nonempty.unsafeFromList (list.range 1 20); in string.unlines [ 133 | (assertEqual [17 18 19 20] (nonempty.takeEnd 4 xs)) 134 | (assertEqual (nonempty.toList xs) (nonempty.takeEnd 100 xs)) 135 | (assertEqual [] (nonempty.takeEnd (-1) xs)) 136 | ]; 137 | 138 | dropEnd = let xs = nonempty.unsafeFromList (list.range 1 20); in string.unlines [ 139 | (assertEqual (list.range 1 16) (nonempty.dropEnd 4 xs)) 140 | (assertEqual [] (nonempty.dropEnd 100 xs)) 141 | (assertEqual (nonempty.toList xs) (nonempty.dropEnd (-1) xs)) 142 | ]; 143 | 144 | length = assertEqual 20 (nonempty.length (nonempty.unsafeFromList (list.range 1 20))); 145 | singleton = assertEqual (nonempty.make 10 []) (nonempty.singleton 10); 146 | map = assertEqual (nonempty.make "foo-0" [ "foo-1" ]) (nonempty.map (i: "foo-" + builtins.toString i) (nonempty.make 0 [ 1 ])); 147 | for = assertEqual (nonempty.make "foo-0" [ "foo-1" ]) (nonempty.for (nonempty.make 0 [1]) (i: "foo-" + builtins.toString i)); 148 | imap = assertEqual (nonempty.make "foo-0" [ "bar-1" ]) (nonempty.imap (i: s: s + "-" + builtins.toString i) (nonempty.make "foo" [ "bar" ])); 149 | ifor = assertEqual (nonempty.make "foo-0" [ "bar-1" ]) (nonempty.ifor (nonempty.make "foo" [ "bar" ]) (i: s: s + "-" + builtins.toString i)); 150 | modifyAt = string.unlines [ 151 | (assertEqual (nonempty.make 1 [ 20 3 ]) (nonempty.modifyAt 1 (x: 10 * x) (nonempty.make 1 [ 2 3 ]))) 152 | (assertEqual (nonempty.make 1 [ 2 3 ]) (nonempty.modifyAt (-3) (x: 10 * x) (nonempty.make 1 [ 2 3 ]))) 153 | ]; 154 | setAt = string.unlines [ 155 | (assertEqual (nonempty.make 1 [ 20 3 ]) (nonempty.setAt 1 20 (nonempty.make 1 [ 2 3 ]))) 156 | (assertEqual (nonempty.make 1 [ 2 3 ]) (nonempty.setAt (-3) 20 (nonempty.make 1 [ 2 3 ]))) 157 | ]; 158 | insertAt = string.unlines [ 159 | (assertEqual (nonempty.make 1 [ 20 2 3 ]) (nonempty.insertAt 1 20 (nonempty.make 1 [ 2 3 ]))) 160 | (assertEqual (nonempty.make 20 [ 1 2 3 ]) (nonempty.insertAt 0 20 (nonempty.make 1 [ 2 3 ]))) 161 | (assertEqual (nonempty.make 1 [ 2 3 20 ]) (nonempty.insertAt 3 20 (nonempty.make 1 [ 2 3 ]))) 162 | ]; 163 | unsafeIndex = string.unlines [ 164 | (assertEqual "bar" (nonempty.unsafeIndex (nonempty.make "bar" ["ry" "barry"]) 0)) 165 | (assertEqual "barry" (nonempty.unsafeIndex (nonempty.make "bar" ["ry" "barry"]) 2)) 166 | ]; 167 | index = string.unlines [ 168 | (assertEqual (optional.just "bar") (nonempty.index (nonempty.make "bar" ["ry" "barry"]) 0)) 169 | (assertEqual (optional.just "barry") (nonempty.index (nonempty.make "bar" ["ry" "barry"]) 2)) 170 | (assertEqual optional.nothing (nonempty.index (nonempty.make "bar" ["ry" "barry"]) 3)) 171 | ]; 172 | filter = assertEqual ["foo" "fun" "friends"] (nonempty.filter (string.hasPrefix "f") (nonempty.make "foo" ["oof" "fun" "nuf" "friends" "sdneirf"])); 173 | elem = assertEqual builtins.true (nonempty.elem "friend" (nonempty.make "texas" ["friend" "amigo"])); 174 | notElem = assertEqual builtins.true (nonempty.notElem "foo" (nonempty.make "texas" ["friend" "amigo"])); 175 | cons = assertEqual (nonempty.make 1 [2 3 4 5]) (nonempty.cons 1 (nonempty.make 2 [3 4 5])); 176 | uncons = assertEqual (tuple2 1 [2 3 4 5]) (nonempty.uncons (nonempty.make 1 [2 3 4 5])); 177 | snoc = assertEqual (nonempty.make 1 [2 3 4 5]) (nonempty.snoc (nonempty.make 1 [2 3 4]) 5); 178 | 179 | foldr = assertEqual 55 (nonempty.foldr builtins.add (nonempty.unsafeFromList (list.range 1 10))); 180 | foldl' = assertEqual 3628800 (nonempty.foldl' builtins.mul (nonempty.unsafeFromList (list.range 1 10))); 181 | foldMap = string.unlines [ 182 | (assertEqual 1 (nonempty.foldMap std.semigroup.first id (nonempty.unsafeFromList (list.range 1 10)))) 183 | (assertEqual 321 ((nonempty.foldMap std.semigroup.endo id (nonempty.make (x: builtins.mul x 3) [ (x: builtins.add x 7) (x: num.pow x 2) ])) 10)) 184 | ]; 185 | fold = string.unlines [ 186 | (assertEqual 1 (nonempty.fold std.semigroup.first (nonempty.unsafeFromList (list.range 1 10)))) 187 | (assertEqual 321 ((nonempty.fold std.semigroup.endo (nonempty.make (x: builtins.mul x 3) [ (x: builtins.add x 7) (x: num.pow x 2) ])) 10)) 188 | ]; 189 | sum = assertEqual 55 (nonempty.sum (nonempty.unsafeFromList (list.range 1 10))); 190 | product = assertEqual 3628800 (nonempty.product (nonempty.unsafeFromList (list.range 1 10))); 191 | any = string.unlines [ 192 | (assertEqual true (nonempty.any num.even (nonempty.make 1 [2 3 4 5]))) 193 | (assertEqual false (nonempty.any num.even (nonempty.make 1 [3 5]))) 194 | ]; 195 | all = string.unlines [ 196 | (assertEqual true (nonempty.all num.even (nonempty.unsafeFromList (list.generate (i: builtins.mul i 2) 10)))) 197 | (assertEqual false (nonempty.all num.even (nonempty.make 2 [4 5 8]))) 198 | ]; 199 | none = string.unlines [ 200 | (assertEqual true (nonempty.none num.odd (nonempty.unsafeFromList (list.generate (i: builtins.mul i 2) 10)))) 201 | (assertEqual false (nonempty.none num.odd (nonempty.make 2 [4 5 8]))) 202 | ]; 203 | count = assertEqual 11 (nonempty.count num.even (nonempty.unsafeFromList (list.generate id 21))); 204 | slice = string.unlines [ 205 | (assertEqual [3 4] (nonempty.slice 2 2 (nonempty.make 1 [2 3 4 5]))) 206 | (assertEqual [3 4 5] (nonempty.slice 2 30 (nonempty.make 1 [2 3 4 5]))) 207 | (assertEqual [2 3 4 5] (nonempty.slice 1 null (nonempty.make 1 [2 3 4 5]))) 208 | ]; 209 | zipWith = assertEqual 210 | (nonempty.make "foo-0" ["foo-1" "foo-2"]) 211 | (nonempty.zipWith (s: i: s + "-" + builtins.toString i) (nonempty.unsafeFromList (list.replicate 10 "foo")) (nonempty.unsafeFromList (list.range 0 2))); 212 | zip = assertEqual 213 | (nonempty.make (tuple2 "foo" 0) [(tuple2 "foo" 1) (tuple2 "foo" 2)]) 214 | (nonempty.zip (nonempty.unsafeFromList (list.replicate 10 "foo")) (nonempty.unsafeFromList (list.range 0 2))); 215 | sequence = string.unlines [ 216 | (let ls = nonempty.unsafeFromList (list.range 1 10); in assertEqual ls (nonempty.sequence nullable.applicative ls)) 217 | (let ls = nonempty.unsafeFromList (list.range 1 10); in assertEqual null (nonempty.sequence nullable.applicative (nonempty.snoc ls null))) 218 | ]; 219 | traverse = string.unlines [ 220 | (let ls = nonempty.unsafeFromList (list.range 1 10); in assertEqual ls (nonempty.traverse nullable.applicative (x: if (num.even x || num.odd x) then x else null) ls)) 221 | ]; 222 | reverse = string.unlines [ 223 | (assertEqual (nonempty.make 3 [2 1]) (nonempty.reverse (nonempty.make 1 [2 3]))) 224 | (assertEqual (nonempty.make 1 []) (nonempty.reverse (nonempty.make 1 []))) 225 | ]; 226 | } 227 | -------------------------------------------------------------------------------- /test/sections/num.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | with { inherit (std.tuple) tuple2; }; 4 | 5 | with (import ./../framework.nix); 6 | 7 | section "std.num" { 8 | negate = assertEqual (num.negate 5) (-5); 9 | abs = string.unlines [ 10 | (assertEqual (num.abs (-5)) 5) 11 | (assertEqual (num.abs 5) 5) 12 | ]; 13 | signum = string.unlines [ 14 | (assertEqual (num.signum 5) 1) 15 | (assertEqual (num.signum 0) 0) 16 | (assertEqual (num.signum (-5)) (-1)) 17 | ]; 18 | min = string.unlines [ 19 | (assertEqual (num.min (-3) 5) (-3)) 20 | (assertEqual (num.min 5 (-3)) (-3)) 21 | ]; 22 | max = string.unlines [ 23 | (assertEqual (num.max (-3) 5) 5) 24 | (assertEqual (num.max 5 (-3)) 5) 25 | ]; 26 | compare = string.unlines [ 27 | (assertEqual (num.compare (-3) 5) "LT") 28 | (assertEqual (num.compare 5 5) "EQ") 29 | (assertEqual (num.compare 5 (-3)) "GT") 30 | (assertEqual (num.compare num.minInt num.maxInt) "LT") 31 | (assertEqual (num.compare num.minInt num.minInt) "EQ") 32 | (assertEqual (num.compare num.maxInt num.minInt) "GT") 33 | ]; 34 | quot = string.unlines [ 35 | (assertEqual (num.quot 18 7) 2) 36 | (assertEqual (num.quot 18 (-7)) (-2)) 37 | (assertEqual (num.quot (-18) 7) (-2)) 38 | (assertEqual (num.quot (-18) (-7)) 2) 39 | ]; 40 | rem = string.unlines [ 41 | (assertEqual (num.rem 18 7) 4) 42 | (assertEqual (num.rem 18 (-7)) 4) 43 | (assertEqual (num.rem (-18) 7) (-4)) 44 | (assertEqual (num.rem (-18) (-7)) (-4)) 45 | ]; 46 | div = string.unlines [ 47 | (assertEqual (num.div 18 7) 2) 48 | (assertEqual (num.div 18 (-7)) (-3)) 49 | (assertEqual (num.div (-18) 7) (-3)) 50 | (assertEqual (num.div (-18) (-7)) 2) 51 | ]; 52 | mod = string.unlines [ 53 | (assertEqual (num.mod 18 7) 4) 54 | (assertEqual (num.mod 18 (-7)) (-3)) 55 | (assertEqual (num.mod (-18) 7) 3) 56 | (assertEqual (num.mod (-18) (-7)) (-4)) 57 | ]; 58 | quotRem = assertEqual (num.quotRem (-18) 7) (tuple2 (-2) (-4)); 59 | divMod = assertEqual (num.divMod (-18) 7) (tuple2 (-3) 3); 60 | even = string.unlines [ 61 | (assertEqual (num.even (-1)) false) 62 | (assertEqual (num.even 0) true) 63 | (assertEqual (num.even 1) false) 64 | ]; 65 | odd = string.unlines [ 66 | (assertEqual (num.odd (-1)) true) 67 | (assertEqual (num.odd 0) false) 68 | (assertEqual (num.odd 1) true) 69 | ]; 70 | fac = string.unlines [ 71 | (assertEqual (num.fac 0) 1) 72 | (assertEqual (num.fac 5) 120) 73 | ]; 74 | pow = string.unlines [ 75 | (assertEqual (num.pow 0 10) 1) 76 | (assertEqual (num.pow 1 10) 1) 77 | (assertEqual (num.pow 2 (-1)) 0) # Fraction 1/2 gets floored 78 | (assertEqual (num.pow 2 0) 1) 79 | (assertEqual (num.pow 2 1) 2) 80 | (assertEqual (num.pow 2 10) 1024) 81 | (assertEqual (num.pow 2 (num.bits.bitSize - 1)) num.minInt) # Overflow 82 | (assertEqual (num.pow 5 3) 125) 83 | (assertEqual (num.pow 10 0) 1) 84 | (assertEqual (num.pow 10 1) 10) 85 | (assertEqual (num.pow 10 3) 1000) 86 | ]; 87 | toFloat = assertEqual (num.toFloat 5) 5.0; 88 | truncate = string.unlines [ 89 | (assertEqual (num.truncate 1.5) 1) 90 | (assertEqual (num.truncate 20.1) 20) 91 | (assertEqual (num.truncate 5.0) 5) 92 | (assertEqual (num.truncate (-5.0)) (-5)) 93 | (assertEqual (num.truncate (-1.5)) (-1)) 94 | (assertEqual (num.truncate (-20.1)) (-20)) 95 | (assertEqual (num.truncate 0) 0) 96 | (assertEqual (num.truncate 0.0) 0) 97 | (assertEqual (num.truncate 1.0e6) 1000000) 98 | (assertEqual (num.truncate (-1.0e6)) (-1000000)) 99 | (assertEqual (num.truncate 1.1e6) 1100000) 100 | (assertEqual (num.truncate (-1.1e6)) (-1100000)) 101 | # num.truncate (num.toFloat num.maxInt) would overflow, but it should 102 | # succeed on an integer regardless 103 | (assertEqual (num.truncate num.maxInt) num.maxInt) 104 | (assertEqual (num.truncate num.minInt) num.minInt) 105 | ]; 106 | floor = string.unlines [ 107 | (assertEqual (num.floor 1.5) 1) 108 | (assertEqual (num.floor (-1.5)) (-2)) 109 | ]; 110 | ceil = string.unlines [ 111 | (assertEqual (num.ceil 1.5) 2) 112 | (assertEqual (num.ceil (-1.5)) (-1)) 113 | ]; 114 | round = string.unlines [ 115 | (assertEqual (num.round 1.5) 2) 116 | (assertEqual (num.round 1.3) 1) 117 | (assertEqual (num.round 1.0) 1) 118 | (assertEqual (num.round (-1.5)) (-2)) 119 | (assertEqual (num.round (-1.3)) (-1)) 120 | (assertEqual (num.round (-1.0)) (-1)) 121 | ]; 122 | tryParseInt = string.unlines [ 123 | (assertEqual (num.tryParseInt "foo") optional.nothing) 124 | (assertEqual (num.tryParseInt "1.0") optional.nothing) 125 | (assertEqual (num.tryParseInt "0") (optional.just 0)) 126 | (assertEqual (num.tryParseInt "05") optional.nothing) 127 | (assertEqual (num.tryParseInt "-5") (optional.just (-5))) 128 | (assertEqual (num.tryParseInt "") optional.nothing) 129 | (assertEqual (num.tryParseInt "-") optional.nothing) 130 | ]; 131 | parseInt = assertEqual (num.parseInt "-5") (-5); 132 | tryParseFloat = string.unlines [ 133 | (assertEqual (num.tryParseFloat "foo") optional.nothing) 134 | (assertEqual (num.tryParseFloat "-1.80") (optional.just (-1.8))) 135 | (assertEqual (num.tryParseFloat "0.0") (optional.just 0.0)) 136 | (assertEqual (num.tryParseFloat "0") (optional.just 0.0)) 137 | (assertEqual (num.tryParseFloat "0.") optional.nothing) 138 | (assertEqual (num.tryParseFloat ".0") optional.nothing) 139 | (assertEqual (num.tryParseFloat ".") optional.nothing) 140 | (assertEqual (num.tryParseFloat "-01.05e-2") optional.nothing) 141 | (assertEqual (num.tryParseFloat "-1.05e-2") (optional.just ((-1.05) / 100))) 142 | ]; 143 | parseFloat = assertEqual (num.parseFloat "-1.80") (-1.8); 144 | toBaseDigits = string.unlines [ 145 | (assertEqual (num.toBaseDigits 16 4660) [ 1 2 3 4 ]) 146 | (assertEqual (num.toBaseDigits 2 85) [ 1 0 1 0 1 0 1 ]) 147 | (assertEqual (num.toBaseDigits 20 0) [ 0 ]) 148 | ]; 149 | fromBaseDigits = string.unlines [ 150 | (assertEqual (num.fromBaseDigits 2 [ 1 0 1 0 1 0 1 ]) 85) 151 | (assertEqual (num.fromBaseDigits 16 [ 1 2 3 4 ]) 4660) 152 | ]; 153 | toHexString = string.unlines [ 154 | (assertEqual (num.toHexString 0) "0") 155 | (assertEqual (num.toHexString 4660) "1234") 156 | (assertEqual (num.toHexString 11259375) "abcdef") 157 | ]; 158 | gcd = string.unlines [ 159 | (assertEqual (num.gcd 0 0) 0) 160 | (assertEqual (num.gcd 1 1) 1) 161 | (assertEqual (num.gcd (-17289472) 198264) 8) 162 | ]; 163 | lcm = string.unlines [ 164 | (assertEqual (num.lcm 0 0) 0) 165 | (assertEqual (num.lcm 1 0) 0) 166 | (assertEqual (num.lcm 1 1) 1) 167 | (assertEqual (num.lcm 127 (-928)) 117856) 168 | ]; 169 | } 170 | -------------------------------------------------------------------------------- /test/sections/optional.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | section "std.optional" { 7 | laws = string.unlines [ 8 | (functor optional.functor { 9 | typeName = "optional"; 10 | identity = { 11 | x = optional.just 5; 12 | }; 13 | composition = { 14 | f = optional.monad.join; 15 | g = optional.just; 16 | x = optional.just "foo"; 17 | }; 18 | }) 19 | (applicative optional.applicative { 20 | typeName = "optional"; 21 | identity = { 22 | v = optional.nothing; 23 | }; 24 | composition = { 25 | u = optional.just (b: builtins.toString (b + 1)); 26 | v = optional.just (a: a + 1); 27 | w = optional.just 5; 28 | }; 29 | homomorphism = { 30 | f = builtins.toString; 31 | x = 5; 32 | }; 33 | interchange = { 34 | u = optional.just (x: x + "-" + x); 35 | y = "foo"; 36 | }; 37 | }) 38 | (monad optional.monad { 39 | typeName = "optional"; 40 | leftIdentity = { 41 | f = x: optional.just (x + x); 42 | x = 5; 43 | }; 44 | rightIdentity = { 45 | x = optional.just 55; 46 | }; 47 | associativity = { 48 | m = optional.just optional.nothing; 49 | f = x: optional.match x { 50 | nothing = optional.just (optional.just 5); 51 | just = k: optional.just (optional.just (k + 1)); 52 | }; 53 | g = x: optional.match x { 54 | nothing = optional.just (optional.just 1); 55 | just = k: optional.just (optional.just (k + 5)); 56 | }; 57 | }; 58 | }) 59 | (semigroup (optional.semigroup list.semigroup) { 60 | typeName = "optional"; 61 | associativity = { 62 | a = optional.just [1 2 3 4]; 63 | b = optional.just [5 6 7 8]; 64 | c = optional.just [9 10]; 65 | }; 66 | }) 67 | (monoid (optional.monoid list.monoid) { 68 | typeName = "optional"; 69 | leftIdentity = { 70 | x = optional.just [1 2 3 4 5]; 71 | }; 72 | rightIdentity = { 73 | x = optional.just ["one" "two" "three" "four" "five"]; 74 | }; 75 | }) 76 | ]; 77 | 78 | match = assertEqual "foobar" 79 | (optional.match (optional.just "foobar") { 80 | nothing = "baz"; 81 | just = function.id; 82 | }); 83 | 84 | isJust = string.unlines [ 85 | (assertEqual true (optional.isJust (optional.just 5))) 86 | (assertEqual true (optional.isJust (optional.just null))) 87 | (assertEqual false (optional.isJust optional.nothing)) 88 | ]; 89 | 90 | isNothing = string.unlines [ 91 | (assertEqual false (optional.isNothing (optional.just 5))) 92 | (assertEqual false (optional.isNothing (optional.just null))) 93 | (assertEqual true (optional.isNothing optional.nothing)) 94 | ]; 95 | 96 | toNullable = string.unlines [ 97 | (assertEqual 1 (optional.toNullable (optional.just 1))) 98 | (assertEqual null (optional.toNullable optional.nothing)) 99 | ]; 100 | } 101 | -------------------------------------------------------------------------------- /test/sections/path.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | let 7 | testDrv = builtins.derivation { 8 | name = "test"; 9 | builder = "test"; 10 | system = "x86_64-linux"; 11 | }; 12 | in section "std.path" { 13 | check = string.unlines [ 14 | (assertEqual true (types.path.check ./foo.nix)) 15 | (assertEqual false (types.path.check (toString ./foo.nix))) 16 | (assertEqual true (types.pathlike.check ./foo.nix)) 17 | (assertEqual true (types.pathlike.check testDrv)) 18 | (assertEqual true (types.pathlike.check (toString ./foo.nix))) 19 | (assertEqual false (types.pathlike.check "a/b/c")) 20 | ]; 21 | 22 | baseName = string.unlines [ 23 | (assertEqual "foo.nix" (path.baseName ./foo.nix)) 24 | (assertEqual "path.nix" (path.baseName ./path.nix)) 25 | (assertEqual "foo.nix" (path.baseName (toString ./foo.nix))) 26 | (assertEqual "-test" (string.substring 32 (-1) (path.baseName testDrv))) 27 | ]; 28 | 29 | dirName = string.unlines [ 30 | (assertEqual ./. (path.parent ./foo.nix)) 31 | (assertEqual ./. (path.parent ./path.nix)) 32 | (assertEqual (toString ./.) (path.dirName (toString ./path.nix))) 33 | (assertEqual (toString builtins.storeDir) (path.dirName testDrv)) 34 | ]; 35 | 36 | fromString = string.unlines [ 37 | (assertEqual (optional.just /a/b/c) (path.fromString "/a/b/c")) 38 | (assertEqual optional.nothing (path.fromString "a/b/c")) 39 | ]; 40 | } 41 | -------------------------------------------------------------------------------- /test/sections/regex.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | section "std.regex" { 7 | escape = assertEqual (regex.escape ''.[]{}()\*+?|^$'') ''\.\[]\{\}\(\)\\\*\+\?\|\^\$''; 8 | 9 | capture = assertEqual (regex.capture "foo") "(foo)"; 10 | 11 | match = string.unlines [ 12 | (assertEqual 13 | (regex.match "([[:alpha:]]+)([[:digit:]]+)" "foo123") 14 | (optional.just ["foo" "123"])) 15 | (assertEqual 16 | (regex.match "([[:alpha:]]+)([[:digit:]]+)" "foobar") 17 | optional.nothing) 18 | ]; 19 | 20 | allMatches = assertEqual (regex.allMatches "[[:digit:]]+" "foo 123 bar 456") ["123" "456"]; 21 | 22 | firstMatch = string.unlines [ 23 | (assertEqual (regex.firstMatch "[aeiou]" "foobar") (optional.just "o")) 24 | (assertEqual (regex.firstMatch "[aeiou]" "xyzzyx") optional.nothing) 25 | ]; 26 | 27 | lastMatch = string.unlines [ 28 | (assertEqual (regex.lastMatch "[aeiou]" "foobar") (optional.just "a")) 29 | (assertEqual (regex.lastMatch "[aeiou]" "xyzzyx") optional.nothing) 30 | ]; 31 | 32 | split = assertEqual (regex.split "(,)" "1,2,3") ["1" [","] "2" [","] "3"]; 33 | 34 | splitOn = assertEqual (regex.splitOn "(,)" "1,2,3") ["1" "2" "3"]; 35 | 36 | substituteWith = assertEqual (regex.substituteWith "([[:digit:]])" (g: builtins.toJSON (builtins.fromJSON (builtins.head g) + 1)) "123") "234"; 37 | 38 | substitute = assertEqual (regex.substitute "[aeiou]" "\\0\\0" "foobar") "foooobaar"; 39 | } 40 | -------------------------------------------------------------------------------- /test/sections/serde.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | 4 | with (import ./../framework.nix); 5 | 6 | let 7 | checkRoundtrip = to: from: data: assertEqual data (from (to data)); 8 | checkRoundtripTOML = checkRoundtrip serde.toTOML serde.fromTOML; 9 | checkRoundtripJSON = checkRoundtrip serde.toJSON serde.fromJSON; 10 | in 11 | section "std.serde" { 12 | toml = 13 | string.unlines [ 14 | # basic k = v notation 15 | (checkRoundtripTOML { foo = 1; }) 16 | # inline JSON-like literals 17 | (checkRoundtripTOML { foo = [1 2 3]; }) 18 | # basic table 19 | (checkRoundtripTOML { foo.bar = 1; }) 20 | # nested tables with dots 21 | (checkRoundtripTOML { foo.bar.baz = 1; }) 22 | # properly escapes invalid identifiers 23 | (checkRoundtripTOML { foo."bar.baz-quux".xyzzy = 1; }) 24 | # double-bracket list notation for lists of dictionaries 25 | (checkRoundtripTOML { foo = [{ bar = 1; } { bar = [{ quux = 2; }]; }]; }) 26 | # mixing dot notation and list notation 27 | (checkRoundtripTOML { foo.bar.baz = [{ bar = 1; } { bar = [{ quux = 2; }]; }]; }) 28 | # filters nulls 29 | (assertEqual (serde.fromTOML (serde.toTOML { foo = 1; bar = null; })) { foo = 1; }) 30 | ]; 31 | 32 | json = 33 | string.unlines [ 34 | # basic k = v notation 35 | (checkRoundtripJSON { foo = 1; }) 36 | # inline JSON-like literals 37 | (checkRoundtripJSON { foo = [1 2 3]; }) 38 | # basic table 39 | (checkRoundtripJSON { foo.bar = 1; }) 40 | # nested tables with dots 41 | (checkRoundtripJSON { foo.bar.baz = 1; }) 42 | # properly escapes invalid identifiers 43 | (checkRoundtripJSON { foo."bar.baz-quux".xyzzy = 1; }) 44 | # double-bracket list notation for lists of dictionaries 45 | (checkRoundtripJSON { foo = [{ bar = 1; } { bar = [{ quux = 2; }]; }]; }) 46 | # mixing dot notation and list notation 47 | (checkRoundtripJSON { foo.bar.baz = [{ bar = 1; } { bar = [{ quux = 2; }]; }]; }) 48 | ]; 49 | } 50 | -------------------------------------------------------------------------------- /test/sections/set.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | with { inherit (std.tuple) tuple2; }; 4 | 5 | with (import ./../framework.nix); 6 | 7 | let 8 | testSet = { a = 0; b = 1; c = 2; }; 9 | in section "std.set" { 10 | check = string.unlines [ 11 | (assertEqual true (types.attrs.check {})) 12 | (assertEqual false (types.attrs.check [])) 13 | (assertEqual true ((types.attrsOf types.int).check testSet)) 14 | (assertEqual true ((types.attrsOf types.int).check {})) 15 | (assertEqual false ((types.attrsOf types.int).check (testSet // { d = "foo"; }))) 16 | ]; 17 | 18 | show = string.unlines [ 19 | (assertEqual "{ a = 0; b = 1; c = 2; }" (types.attrs.show testSet)) 20 | (assertEqual "{ }" (types.attrs.show {})) 21 | ]; 22 | 23 | empty = assertEqual set.empty {}; 24 | optional = string.unlines [ 25 | (assertEqual set.empty (set.optional false testSet)) 26 | (assertEqual testSet (set.optional true testSet)) 27 | ]; 28 | keys = assertEqual ["a" "b" "c"] (set.keys testSet); 29 | values = assertEqual [0 1 2] (set.values testSet); 30 | map = assertEqual { a = 1; b = 2; c = 3; } (set.map (_: num.add 1) testSet); 31 | zip = assertEqual { a = [0 1]; b = [1]; c = [2]; } (set.mapZip (_: function.id) [testSet { a = 1; }]); 32 | without = assertEqual { b = 1; c = 2; } (set.without [ "a" ] testSet); 33 | retain = assertEqual { a = 0; } (set.retain [ "a" ] testSet); 34 | mapToValues = assertEqual [ 1 2 3 ] (set.mapToValues (_: num.add 1) testSet); 35 | filter = assertEqual { b = 1; } (set.filter (k: v: v == 1) testSet); 36 | traverse = assertEqual testSet (set.traverse nullable.applicative (x: if (num.even x || num.odd x) then x else null) testSet); 37 | toList = assertEqual [ 38 | (tuple2 "a" 0) 39 | (tuple2 "b" 1) 40 | (tuple2 "c" 2) 41 | ] (set.toList testSet); 42 | fromList = assertEqual testSet (set.fromList (set.toList testSet)); 43 | gen = assertEqual (set.gen [ "a" "b" ] id) { a = "a"; b = "b"; }; 44 | 45 | get = string.unlines [ 46 | (assertEqual (set.get "a" { a = 0; }) (optional.just 0)) 47 | (assertEqual (set.get "a" { }) optional.nothing) 48 | (assertEqual (set.unsafeGet "a" { a = 0; }) 0) 49 | ]; 50 | getOr = assertEqual (set.getOr 0 "a" {}) 0; 51 | 52 | at = string.unlines [ 53 | (assertEqual (set.at [ "a" "b" ] { a.b = 0; }) (optional.just 0)) 54 | (assertEqual (set.at [ "a" "c" ] { a.b = 0; }) optional.nothing) 55 | (assertEqual (set.at [ "a" "b" "c" ] { a.b = 0; }) optional.nothing) 56 | (assertEqual (set.unsafeAt [ "a" "b" ] { a.b = 0; }) 0) 57 | ]; 58 | atOr = assertEqual (set.atOr null [ "a" "b" "c" ] { a.b = 0; }) null; 59 | 60 | assign = assertEqual (set.assign "a" 0 { }) { a = 0; }; 61 | assignAt = assertEqual (set.assignAt [ "a" ] 0 { }) { a = 0; }; 62 | assignAtPath = assertEqual (set.assignAt [ "a" "x" ] 0 { }) { a.x = 0; }; 63 | assignAtEmpty = assertEqual (set.assignAt [ ] { a = 0; } testSet) { a = 0; }; 64 | assignAtMerge = assertEqual (set.assignAt [ "x" "y" ] 0 testSet) (testSet // { x.y = 0; }); 65 | } 66 | -------------------------------------------------------------------------------- /test/sections/string.nix: -------------------------------------------------------------------------------- 1 | with { std = import ./../../default.nix; }; 2 | with std; 3 | with { inherit (std.tuple) tuple2; }; 4 | 5 | with (import ./../framework.nix); 6 | 7 | section "std.string" { 8 | check = string.unlines [ 9 | (assertEqual true (types.string.check "foo")) 10 | (assertEqual false (types.string.check ./foo)) 11 | (assertEqual true (types.stringlike.check ./foo)) 12 | (assertEqual true (types.stringlike.check { outPath = ./foo; })) 13 | (assertEqual true (types.stringlike.check { __toString = _ "foo"; })) 14 | (assertEqual false (types.stringlike.check { })) 15 | ]; 16 | 17 | show = string.unlines [ 18 | (assertEqual ''"foo"'' (types.string.show "foo")) 19 | (assertEqual ''"foo"'' (types.stringlike.show { __toString = _: "foo"; })) 20 | ]; 21 | 22 | laws = string.unlines [ 23 | (semigroup string.semigroup { 24 | typeName = "string"; 25 | associativity = { 26 | a = "foo"; 27 | b = "bar"; 28 | c = "baz"; 29 | }; 30 | }) 31 | (monoid string.monoid { 32 | typeName = "string"; 33 | leftIdentity = { 34 | x = "foo"; 35 | }; 36 | rightIdentity = { 37 | x = "bar"; 38 | }; 39 | }) 40 | ]; 41 | substring = string.unlines [ 42 | (assertEqual (string.substring 2 3 "foobar") "oba") 43 | (assertEqual (string.substring 4 7 "foobar") "ar") 44 | (assertEqual (string.substring 10 5 "foobar") string.empty) 45 | (assertEqual (string.substring 1 (-20) "foobar") "oobar") 46 | ]; 47 | unsafeIndex = assertEqual (string.unsafeIndex "foobar" 3) "b"; 48 | index = string.unlines [ 49 | (assertEqual (string.index "foobar" 3) (optional.just "b")) 50 | (assertEqual (string.index "foobar" 6) optional.nothing) 51 | (assertEqual (string.index "foobar" (-1)) optional.nothing) 52 | ]; 53 | length = assertEqual (string.length "foo") 3; 54 | empty = string.unlines [ 55 | (assertEqual (string.isEmpty "a") false) 56 | (assertEqual (string.isEmpty string.empty) true) 57 | ]; 58 | replace = assertEqual (string.replace ["o" "a"] ["u " "e "] "foobar") "fu u be r"; 59 | concat = assertEqual (string.concat ["foo" "bar"]) "foobar"; 60 | concatSep = assertEqual (string.concatSep ", " ["1" "2" "3"]) "1, 2, 3"; 61 | concatMap = assertEqual (string.concatMap builtins.toJSON [ 1 2 3 ]) "123"; 62 | concatMapSep = assertEqual (string.concatMapSep ", " builtins.toJSON [ 1 2 3 ]) "1, 2, 3"; 63 | concatImap = assertEqual (string.concatImap (i: x: x + builtins.toJSON i) [ "foo" "bar" "baz" ]) "foo0bar1baz2"; 64 | concatImapSep = assertEqual (string.concatImapSep "\n" (i: x: builtins.toJSON (i + 1) + ": " + x) [ "foo" "bar" "baz" ]) "1: foo\n2: bar\n3: baz"; 65 | toChars = assertEqual (string.toChars "foo") ["f" "o" "o"]; 66 | fromChars = assertEqual (string.fromChars ["f" "o" "o"]) "foo"; 67 | map = assertEqual (string.map (x: x + " ") "foo") "f o o "; 68 | imap = assertEqual (string.imap (i: x: builtins.toJSON i + x) "foo") "0f1o2o"; 69 | filter = assertEqual (string.filter (x: x != " ") "foo bar baz") "foobarbaz"; 70 | findIndex = assertEqual (string.findIndex (x: x == " ") "foo bar baz") (optional.just 3); 71 | findLastIndex = assertEqual (string.findLastIndex (x: x == " ") "foo bar baz") (optional.just 7); 72 | find = string.unlines [ 73 | (assertEqual (string.find (x: x == " ") "foo bar baz") (optional.just " ")) 74 | (assertEqual (string.find (x: x == "q") "foo bar baz") optional.nothing) 75 | ]; 76 | findLast = string.unlines [ 77 | (assertEqual (string.find (x: x == " ") "foo bar baz") (optional.just " ")) 78 | (assertEqual (string.find (x: x == "q") "foo bar baz") optional.nothing) 79 | ]; 80 | escape = assertEqual (string.escape ["$"] "foo$bar") "foo\\$bar"; 81 | escapeShellArg = assertEqual (string.escapeShellArg "foo 'bar' baz") "'foo '\\''bar'\\'' baz'"; 82 | escapeNixString = assertEqual (string.escapeNixString "foo$bar") ''"foo\$bar"''; 83 | hasPrefix = string.unlines [ 84 | (assertEqual (string.hasPrefix "foo" "foobar") true) 85 | (assertEqual (string.hasPrefix "foo" "barfoo") false) 86 | (assertEqual (string.hasPrefix "foo" string.empty) false) 87 | ]; 88 | hasSuffix = string.unlines [ 89 | (assertEqual (string.hasSuffix "foo" "barfoo") true) 90 | (assertEqual (string.hasSuffix "foo" "foobar") false) 91 | (assertEqual (string.hasSuffix "foo" string.empty) false) 92 | ]; 93 | hasInfix = string.unlines [ 94 | (assertEqual (string.hasInfix "bar" "foobarbaz") true) 95 | (assertEqual (string.hasInfix "foo" "foobar") true) 96 | (assertEqual (string.hasInfix "bar" "foobar") true) 97 | ]; 98 | removePrefix = string.unlines [ 99 | (assertEqual (string.removePrefix "/" "/foo") "foo") 100 | (assertEqual (string.removePrefix "/" "foo") "foo") 101 | ]; 102 | removeSuffix = string.unlines [ 103 | (assertEqual (string.removeSuffix ".nix" "foo.nix") "foo") 104 | (assertEqual (string.removeSuffix ".nix" "foo") "foo") 105 | ]; 106 | count = assertEqual (string.count "." ".a.b.c.d.") 5; 107 | optional = string.unlines [ 108 | (assertEqual (string.optional true "foo") "foo") 109 | (assertEqual (string.optional false "foo") string.empty) 110 | ]; 111 | unsafeHead = assertEqual (string.unsafeHead "bar") "b"; 112 | unsafeTail = assertEqual (string.unsafeTail "bar") "ar"; 113 | unsafeInit = assertEqual (string.unsafeInit "bar") "ba"; 114 | unsafeLast = assertEqual (string.unsafeLast "bar") "r"; 115 | head = string.unlines [ 116 | (assertEqual (string.head "bar") (optional.just "b")) 117 | (assertEqual (string.head "") optional.nothing) 118 | ]; 119 | tail = string.unlines [ 120 | (assertEqual (string.tail "bar") (optional.just "ar")) 121 | (assertEqual (string.tail "") optional.nothing) 122 | ]; 123 | init = string.unlines [ 124 | (assertEqual (string.init "bar") (optional.just "ba")) 125 | (assertEqual (string.init "") optional.nothing) 126 | ]; 127 | last = string.unlines [ 128 | (assertEqual (string.last "bar") (optional.just "r")) 129 | (assertEqual (string.last "") optional.nothing) 130 | ]; 131 | take = string.unlines [ 132 | (assertEqual (string.take 3 "foobar") "foo") 133 | (assertEqual (string.take 7 "foobar") "foobar") 134 | (assertEqual (string.take (-1) "foobar") string.empty) 135 | ]; 136 | drop = string.unlines [ 137 | (assertEqual (string.drop 3 "foobar") "bar") 138 | (assertEqual (string.drop 7 "foobar") string.empty) 139 | (assertEqual (string.drop (-1) "foobar") "foobar") 140 | ]; 141 | takeEnd = string.unlines [ 142 | (assertEqual (string.takeEnd 3 "foobar") "bar") 143 | (assertEqual (string.takeEnd 7 "foobar") "foobar") 144 | (assertEqual (string.takeEnd (-1) "foobar") string.empty) 145 | ]; 146 | dropEnd = string.unlines [ 147 | (assertEqual (string.dropEnd 3 "foobar") "foo") 148 | (assertEqual (string.dropEnd 7 "foobar") string.empty) 149 | (assertEqual (string.dropEnd (-1) "foobar") "foobar") 150 | ]; 151 | takeWhile = assertEqual (string.takeWhile (x: x != " ") "foo bar baz") "foo"; 152 | dropWhile = assertEqual (string.dropWhile (x: x != " ") "foo bar baz") " bar baz"; 153 | takeWhileEnd = assertEqual (string.takeWhileEnd (x: x != " ") "foo bar baz") "baz"; 154 | dropWhileEnd = assertEqual (string.dropWhileEnd (x: x != " ") "foo bar baz") "foo bar "; 155 | splitAt = assertEqual (string.splitAt 3 "foobar") (tuple2 "foo" "bar"); 156 | span = assertEqual (string.span (x: x != " ") "foo bar baz") (tuple2 "foo" " bar baz"); 157 | break = assertEqual (string.break (x: x == " ") "foo bar baz") (tuple2 "foo" " bar baz"); 158 | reverse = string.unlines [ 159 | (assertEqual (string.reverse "foobar") "raboof") 160 | (assertEqual (string.reverse string.empty) string.empty) 161 | ]; 162 | replicate = string.unlines [ 163 | (assertEqual (string.replicate 3 "foo") "foofoofoo") 164 | (assertEqual (string.replicate 0 "bar") string.empty) 165 | ]; 166 | lines = string.unlines [ 167 | (assertEqual (string.lines "foo\nbar\n") [ "foo" "bar" ]) 168 | (assertEqual (string.lines "foo\nbar") [ "foo" "bar" ]) 169 | (assertEqual (string.lines "\n") [ string.empty ]) 170 | (assertEqual (string.lines string.empty) []) 171 | ]; 172 | unlines = string.unlines [ 173 | (assertEqual (string.unlines [ "foo" "bar" ]) "foo\nbar\n") 174 | (assertEqual (string.unlines []) string.empty) 175 | ]; 176 | words = string.unlines [ 177 | (assertEqual (string.words "foo \t bar ") [ "foo" "bar" ]) 178 | (assertEqual (string.words " ") []) 179 | (assertEqual (string.words string.empty) []) 180 | ]; 181 | unwords = assertEqual (string.unwords [ "foo" "bar" ]) "foo bar"; 182 | intercalate = assertEqual (string.intercalate ", " ["1" "2" "3"]) "1, 2, 3"; 183 | toLower = string.unlines [ 184 | (assertEqual (string.toLower "FOO bar") "foo bar") 185 | (assertEqual (string.toLower (string.fromChars (string.upperChars ++ string.lowerChars))) (string.fromChars ((string.lowerChars ++ string.lowerChars)))) 186 | ]; 187 | toUpper = string.unlines [ 188 | (assertEqual (string.toUpper "FOO bar") "FOO BAR") 189 | (assertEqual (string.toUpper (string.fromChars (string.upperChars ++ string.lowerChars))) (string.fromChars ((string.upperChars ++ string.upperChars)))) 190 | ]; 191 | isLower = string.unlines ( 192 | list.map (c: assertEqual (string.isLower c) true) (string.toChars "abcdefghijklmnopqrstuv") 193 | ++ list.map (c: assertEqual (string.isLower c) false) (string.toChars "ABCDEFGHIJKLMNOPQRSTUVWXYZ") 194 | ); 195 | isUpper = string.unlines ( 196 | list.map (c: assertEqual (string.isUpper c) true) (string.toChars "ABCDEFGHIJKLMNOPQRSTUVWXYZ") 197 | ++ list.map (c: assertEqual (string.isUpper c) false) (string.toChars "abcdefghijklmnopqrstuv") 198 | ); 199 | strip = string.unlines [ 200 | (assertEqual (string.strip " \t\t foo \t") "foo") 201 | (assertEqual (string.strip " \t\t \t") string.empty) 202 | ]; 203 | stripStart = assertEqual (string.stripStart " \t\t foo \t") "foo \t"; 204 | stripEnd = assertEqual (string.stripEnd " \t\t foo \t") " \t\t foo"; 205 | justifyLeft = string.unlines [ 206 | (assertEqual (string.justifyLeft 7 "x" "foo") "fooxxxx") 207 | (assertEqual (string.justifyLeft 2 "x" "foo") "foo") 208 | (assertEqual (string.justifyLeft 9 "xyz" "foo") "fooxyzxyz") 209 | (assertEqual (string.justifyLeft 7 "xyz" "foo") "fooxyzx") 210 | ]; 211 | justifyRight = string.unlines [ 212 | (assertEqual (string.justifyRight 7 "x" "foo") "xxxxfoo") 213 | (assertEqual (string.justifyRight 2 "x" "foo") "foo") 214 | (assertEqual (string.justifyRight 9 "xyz" "foo") "xyzxyzfoo") 215 | (assertEqual (string.justifyRight 7 "xyz" "foo") "xyzxfoo") 216 | ]; 217 | justifyCenter = string.unlines [ 218 | (assertEqual (string.justifyCenter 7 "x" "foo") "xxfooxx") 219 | (assertEqual (string.justifyCenter 2 "x" "foo") "foo") 220 | (assertEqual (string.justifyCenter 8 "xyz" "foo") "xyfooxyz") 221 | ]; 222 | camelTo = string.unlines [ 223 | (assertEqual (string.camelTo "_" "CamelAPICase") "camel_api_case") 224 | (assertEqual (string.camelTo "-" "BlahFooBlah") "blah-foo-blah") 225 | (assertEqual (string.camelTo "@" "blahblahblah") "blahblahblah") 226 | (assertEqual (string.camelTo "!" "blahblahBlah") "blahblah!blah") 227 | ]; 228 | 229 | convert = string.unlines [ 230 | (assertEqual (optional.just "foo") (string.convert "foo")) 231 | (assertEqual (optional.just "foo") (string.convert { __toString = _: "foo"; })) 232 | (assertEqual (optional.just "/foo") (string.convert { outPath = "/foo"; })) 233 | (assertEqual optional.nothing (string.convert { })) 234 | (assertEqual (optional.just (toString ./default.nix)) (string.convert ./default.nix)) 235 | (assertEqual (optional.just "") (string.convert null)) 236 | (assertEqual (optional.just "1") (string.convert 1)) 237 | (assertEqual (optional.just (toString 1.0)) (string.convert 1.0)) 238 | (assertEqual (optional.just "1") (string.convert true)) 239 | (assertEqual (optional.just "") (string.convert false)) 240 | (assertEqual (optional.just "") (string.convert [ ])) 241 | (assertEqual (optional.just "foo") (string.convert [ "foo" ])) 242 | (assertEqual (optional.just "foo 1 ") (string.convert [ "foo" 1 null ])) 243 | (assertEqual optional.nothing (string.convert ({ a }: a))) 244 | ]; 245 | coerce = string.unlines [ 246 | (assertEqual (optional.just "foo") (string.coerce "foo")) 247 | (assertEqual (optional.just "foo") (string.coerce { __toString = _: "foo"; })) 248 | (assertEqual (optional.just "/foo") (string.coerce { outPath = "/foo"; })) 249 | (assertEqual optional.nothing (string.coerce { })) 250 | (assertEqual true (optional.isJust (string.coerce ./default.nix))) 251 | (assertEqual optional.nothing (string.coerce null)) 252 | (assertEqual optional.nothing (string.coerce 1)) 253 | (assertEqual optional.nothing (string.coerce 1.0)) 254 | (assertEqual optional.nothing (string.coerce true)) 255 | (assertEqual optional.nothing (string.coerce [ "foo" ])) 256 | (assertEqual optional.nothing (string.coerce ({ a }: a))) 257 | ]; 258 | } 259 | -------------------------------------------------------------------------------- /tuple.nix: -------------------------------------------------------------------------------- 1 | { 2 | tuple0 = { }; 3 | tuple1 = _0: { inherit _0; }; 4 | tuple2 = _0: _1: { inherit _0 _1; }; 5 | tuple3 = _0: _1: _2: { inherit _0 _1 _2; }; 6 | tuple4 = _0: _1: _2: _3: { inherit _0 _1 _2 _3; }; 7 | tuple5 = _0: _1: _2: _3: _4: { inherit _0 _1 _2 _3 _4; }; 8 | tuple6 = _0: _1: _2: _3: _4: _5: { inherit _0 _1 _2 _3 _4 _5; }; 9 | tuple7 = _0: _1: _2: _3: _4: _5: _6: { inherit _0 _1 _2 _3 _4 _5 _6; }; 10 | tuple8 = _0: _1: _2: _3: _4: _5: _6: _7: { inherit _0 _1 _2 _3 _4 _5 _6 _7; }; 11 | tuple9 = _0: _1: _2: _3: _4: _5: _6: _7: _8: { inherit _0 _1 _2 _3 _4 _5 _6 _7 _8; }; 12 | tuple10 = _0: _1: _2: _3: _4: _5: _6: _7: _8: _9: { inherit _0 _1 _2 _3 _4 _5 _6 _7 _8 _9; }; 13 | 14 | map1 = f0: { _0 }: { _0 = f0 _0; }; 15 | map2 = f0: f1: { _0, _1 }: { _0 = f0 _0; _1 = f1 _1; }; 16 | map3 = f0: f1: f2: { _0, _1, _2 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; }; 17 | map4 = f0: f1: f2: f3: { _0, _1, _2, _3 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; }; 18 | map5 = f0: f1: f2: f3: f4: { _0, _1, _2, _3, _4 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; _4 = f4 _4; }; 19 | map6 = f0: f1: f2: f3: f4: f5: { _0, _1, _2, _3, _4, _5 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; _4 = f4 _4; _5 = f5 _5; }; 20 | map7 = f0: f1: f2: f3: f4: f5: f6: { _0, _1, _2, _3, _4, _5, _6 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; _4 = f4 _4; _5 = f5 _5; _6 = f6 _6; }; 21 | map8 = f0: f1: f2: f3: f4: f5: f6: f7: { _0, _1, _2, _3, _4, _5, _6, _7 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; _4 = f4 _4; _5 = f5 _5; _6 = f6 _6; _7 = f7 _7; }; 22 | map9 = f0: f1: f2: f3: f4: f5: f6: f7: f8: { _0, _1, _2, _3, _4, _5, _6, _7, _8 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; _4 = f4 _4; _5 = f5 _5; _6 = f6 _6; _7 = f7 _7; _8 = f8 _8; }; 23 | map10 = f0: f1: f2: f3: f4: f5: f6: f7: f8: f9: { _0, _1, _2, _3, _4, _5, _6, _7, _8, _9 }: { _0 = f0 _0; _1 = f1 _1; _2 = f2 _2; _3 = f3 _3; _4 = f4 _4; _5 = f5 _5; _6 = f6 _6; _7 = f7 _7; _8 = f8 _8; _9 = f9 _9; }; 24 | 25 | over0 = f: x@{ _0, ... }: x // { _0 = f _0; }; 26 | over1 = f: x@{ _1, ... }: x // { _1 = f _1; }; 27 | over2 = f: x@{ _2, ... }: x // { _2 = f _2; }; 28 | over3 = f: x@{ _3, ... }: x // { _3 = f _3; }; 29 | over4 = f: x@{ _4, ... }: x // { _4 = f _4; }; 30 | over5 = f: x@{ _5, ... }: x // { _5 = f _5; }; 31 | over6 = f: x@{ _6, ... }: x // { _6 = f _6; }; 32 | over7 = f: x@{ _7, ... }: x // { _7 = f _7; }; 33 | over8 = f: x@{ _8, ... }: x // { _8 = f _8; }; 34 | over9 = f: x@{ _9, ... }: x // { _9 = f _9; }; 35 | over10 = f: x@{ _10, ... }: x // { _10 = f _10; }; 36 | 37 | /* toPair :: (a, b) -> { name : a, value : b } 38 | 39 | Converts a 2-tuple to the argument required by e.g. `builtins.listToAttrs` 40 | */ 41 | toPair = { _0, _1 }: { name = _0; value = _1; }; 42 | } 43 | -------------------------------------------------------------------------------- /types.nix: -------------------------------------------------------------------------------- 1 | with rec { 2 | function = import ./function.nix; 3 | inherit (function) compose const flip; 4 | 5 | tuple = import ./tuple.nix; 6 | inherit (tuple) tuple2; 7 | }; 8 | 9 | let 10 | imports = { 11 | bool = import ./bool.nix; 12 | function = import ./function.nix; 13 | list = import ./list.nix; 14 | nonempty = import ./nonempty.nix; 15 | num = import ./num.nix; 16 | optional = import ./optional.nix; 17 | set = import ./set.nix; 18 | string = import ./string.nix; 19 | types = import ./types.nix; 20 | }; 21 | inherit (imports.function) const; 22 | _null = null; 23 | 24 | /* unsafe show functions */ 25 | showList = show: ls: 26 | let body = imports.string.intercalate ", " (imports.list.map show ls); 27 | tokens = [ "[" ] ++ imports.list.optional (! imports.list.empty ls) body ++ [ "]" ]; 28 | in imports.string.intercalate " " tokens; 29 | showSet = show: s: 30 | let showKey = k: 31 | let v = s.${k}; 32 | in "${k} = ${show v};"; 33 | body = imports.list.map showKey (imports.set.keys s); 34 | in imports.string.intercalate " " ([ "{" ] ++ body ++ [ "}" ]); 35 | showNonEmpty = show: x: 36 | "nonempty " + showList show (imports.nonempty.toList x); 37 | 38 | addCheck = type: check: type // { 39 | check = x: type.check x && check x; 40 | }; 41 | 42 | between = lo: hi: 43 | addCheck imports.types.int (x: x >= lo && x <= hi) // { 44 | name = "intBetween"; 45 | description = "an integer in [${builtins.toString lo}, ${builtins.toString hi}]"; 46 | }; 47 | 48 | unsigned = bit: lo: hi: 49 | between lo hi // { 50 | name = "u${builtins.toString bit}"; 51 | description = "an unsigned integer in [${builtins.toString lo}, ${builtins.toString hi}]"; 52 | }; 53 | 54 | signed = bit: lo: hi: 55 | between lo hi // { 56 | name = "i${builtins.toString bit}"; 57 | description = "a signed integer in [${builtins.toString lo}, ${builtins.toString hi}]"; 58 | }; 59 | 60 | in 61 | rec { 62 | /* show :: a -> string */ 63 | show = x: (imports.types.of x).show x; 64 | 65 | /* of :: a -> type */ 66 | of = let 67 | types = imports.set.map (_: imports.nonempty.singleton) { 68 | inherit (imports.types) bool float int null list path lambda; 69 | } // { 70 | set = imports.nonempty.make imports.types.attrs [ 71 | imports.types.stringlike 72 | imports.types.functionSet 73 | ]; 74 | string = imports.nonempty.make imports.types.string [ 75 | imports.types.path 76 | ]; 77 | }; 78 | findType = types: x: (imports.nonempty.foldl' 79 | (c: n: if n.check x then n else c) 80 | types); 81 | in x: findType types.${builtins.typeOf x} or (imports.nonempty.singleton imports.types.any) x; 82 | 83 | /* 84 | type Type = { 85 | name :: string, 86 | check :: a -> bool, 87 | show :: a -> string, 88 | } 89 | */ 90 | 91 | mkType = { 92 | name, 93 | check, 94 | description, 95 | show ? imports.types.show 96 | }: { 97 | inherit name check description show; 98 | }; 99 | 100 | any = mkType { 101 | name = "any"; 102 | description = "any value"; 103 | check = const true; 104 | }; 105 | 106 | bool = mkType { 107 | name = "bool"; 108 | description = "boolean"; 109 | check = builtins.isBool; 110 | show = x: 111 | if x == true 112 | then "true" 113 | else "false"; 114 | }; 115 | 116 | int = mkType { 117 | name = "int"; 118 | description = "machine integer"; 119 | check = builtins.isInt; 120 | show = builtins.toString; 121 | }; 122 | 123 | i8 = signed 8 (-128) 127; 124 | i16 = signed 16 (-32768) 32767; 125 | i32 = signed 32 (-2147483648) 2147483647; 126 | #i64 = signed 64 (-9223372036854775808) 9223372036854775807; 127 | u8 = unsigned 8 0 255; 128 | u16 = unsigned 16 0 65535; 129 | u32 = unsigned 32 0 4294967295; 130 | #u64 = signed 64 0 18446744073709551615; 131 | 132 | /* Port numbers. Alias of u16 */ 133 | port = u16; 134 | 135 | float = mkType { 136 | name = "f32"; 137 | description = "32-bit floating point number"; 138 | check = builtins.isFloat; 139 | show = builtins.toString; 140 | }; 141 | 142 | string = mkType { 143 | name = "string"; 144 | description = "string"; 145 | check = builtins.isString; 146 | show = imports.string.escapeNixString; 147 | }; 148 | 149 | stringlike = mkType { 150 | name = "stringlike"; 151 | description = "stringlike"; 152 | check = compose imports.optional.isJust imports.string.coerce; 153 | inherit (string) show; 154 | }; 155 | 156 | stringMatching = pattern: mkType { 157 | name = "stringMatching ${imports.string.escapeNixString pattern}"; 158 | description = "string matching the pattern ${pattern}"; 159 | check = x: string.check x && builtins.match pattern x != _null; 160 | }; 161 | 162 | attrs = mkType { 163 | name = "attrs"; 164 | description = "attribute set"; 165 | check = builtins.isAttrs; 166 | show = showSet show; 167 | }; 168 | 169 | attrsOf = type: 170 | let check = x: imports.list.all type.check (imports.set.values x); 171 | in addCheck attrs check // { 172 | name = "${attrs.name} ${type.name}"; 173 | description = "${attrs.description} of ${type.description}s"; 174 | show = showSet type.show; 175 | }; 176 | 177 | drv = mkType { 178 | name = "derivation"; 179 | description = "derivation"; 180 | check = x: builtins.isAttrs x && x.type or null == "derivation"; 181 | }; 182 | 183 | path = mkType { 184 | name = "path"; 185 | description = "a path"; 186 | check = builtins.isPath; 187 | show = builtins.toString; 188 | }; 189 | 190 | pathlike = 191 | let check = x: imports.string.substring 0 1 (toString x) == "/"; 192 | in addCheck stringlike check // { 193 | name = "pathlike"; 194 | description = "a pathlike string"; 195 | show = builtins.toString; 196 | }; 197 | 198 | list = mkType { 199 | name = "list"; 200 | description = "list"; 201 | check = builtins.isList; 202 | show = showList show; 203 | }; 204 | 205 | listOf = type: 206 | let check = x: imports.list.all type.check x; 207 | in addCheck list check // { 208 | name = "[${type.name}]"; 209 | description = "list of ${type.description}s"; 210 | show = showList type.show; 211 | }; 212 | 213 | nonEmptyList = 214 | let base = addCheck list (x: ! imports.list.empty x); 215 | in base // { 216 | name = "nonempty ${base.name}"; 217 | description = "non-empty ${base.description}"; 218 | }; 219 | 220 | nonEmptyListOf = type: 221 | let base = addCheck (listOf type) (x: x != []); 222 | in base // { 223 | name = "nonempty ${base.name}"; 224 | description = "non-empty " + base.description; 225 | }; 226 | 227 | nonEmpty = mkType { 228 | name = "nonempty"; 229 | description = "non-empty"; 230 | check = x: list.check x.tail or null && imports.set.keys x == [ "head" "tail" ]; 231 | show = showNonEmpty show; 232 | }; 233 | 234 | nonEmptyOf = type: let 235 | tail = listOf type; 236 | check = x: type.check x.head && tail.check x.tail; 237 | base = addCheck nonEmpty check; 238 | in base // { 239 | name = "${base.name} ${type.name}"; 240 | description = "${base.description} of ${type.description}s"; 241 | show = showNonEmpty type.show; 242 | }; 243 | 244 | null = mkType { 245 | name = "null"; 246 | description = "null"; 247 | check = x: x == _null; 248 | show = const "null"; 249 | }; 250 | 251 | nullOr = type: either null type; 252 | 253 | enum = values: 254 | let showType = v: 255 | if builtins.isString v 256 | then ''"${v}"'' 257 | else if builtins.isInt v 258 | then builtins.toString v 259 | else ''<${builtins.typeOf v}>''; 260 | in mkType { 261 | name = "enum"; 262 | description = "one of ${imports.string.concatMapSep ", " showType values}"; 263 | check = imports.function.flip imports.list.elem values; 264 | }; 265 | 266 | either = a: b: mkType { 267 | name = "either"; 268 | description = "${a.description} or ${b.description}"; 269 | check = x: a.check x || b.check x; 270 | show = x: imports.bool.ifThenElse (a.check x) a.show b.show x; 271 | }; 272 | 273 | oneOf = types: 274 | let ht = imports.list.match types { 275 | nil = throw "types.oneOf needs at least one type in its argument"; 276 | cons = x: xs: tuple2 x xs; 277 | }; 278 | in imports.list.foldl' either ht._0 ht._1; 279 | 280 | function = mkType { 281 | name = "function"; 282 | description = "function"; 283 | check = f: lambda.check f || functionSet.check f; 284 | show = f: let 285 | args = imports.function.args f; 286 | showArg = k: isOptional: imports.bool.ifThenElse isOptional "${k} ? «code»" k; 287 | body = imports.string.intercalate ", " (imports.set.mapToValues showArg args); 288 | withArgs = "{ " + body + " }: «code»"; 289 | in imports.bool.ifThenElse (args == { }) "«lambda»" withArgs; 290 | }; 291 | 292 | lambda = mkType { 293 | name = "lambda"; 294 | description = "lambda function"; 295 | check = builtins.isFunction; 296 | inherit (function) show; 297 | }; 298 | 299 | functionSet = mkType { 300 | name = "function ${attrs.name}"; 301 | description = "callable ${attrs.description} function"; 302 | check = f: f ? __functor && function.check f.__functor && function.check (f.__functor f); 303 | inherit (function) show; 304 | }; 305 | } 306 | -------------------------------------------------------------------------------- /version.nix: -------------------------------------------------------------------------------- 1 | "0.0.0.1" 2 | --------------------------------------------------------------------------------