From 7c1dc8605f1bbf907dba14a6309cde52c722437b Mon Sep 17 00:00:00 2001 From: Emile Clark-Boman Date: Sun, 25 Jan 2026 11:22:55 +1000 Subject: [PATCH] segment util/util.nix into std/* --- nt/primitives/std/README.md | 17 ++++ nt/primitives/std/attrs.nix | 73 ++++++++++++++ nt/primitives/std/default.nix | 28 ++++++ nt/primitives/std/enforce.nix | 2 + nt/primitives/std/fn.nix | 10 ++ nt/primitives/std/list.nix | 71 ++++++++++++++ nt/primitives/std/num.nix | 30 ++++++ nt/primitives/std/string.nix | 25 +++++ nt/primitives/util/enforce.nix | 13 +-- nt/primitives/util/util.nix | 172 --------------------------------- 10 files changed, 257 insertions(+), 184 deletions(-) create mode 100644 nt/primitives/std/README.md create mode 100644 nt/primitives/std/attrs.nix create mode 100644 nt/primitives/std/default.nix create mode 100644 nt/primitives/std/enforce.nix create mode 100644 nt/primitives/std/fn.nix create mode 100644 nt/primitives/std/list.nix create mode 100644 nt/primitives/std/num.nix create mode 100644 nt/primitives/std/string.nix delete mode 100644 nt/primitives/util/util.nix diff --git a/nt/primitives/std/README.md b/nt/primitives/std/README.md new file mode 100644 index 0000000..9563d0b --- /dev/null +++ b/nt/primitives/std/README.md @@ -0,0 +1,17 @@ +## Primitive Standard Functions +>[!NOTE] +> This directory is dedicated to porting functions from `nixpkgs.pkgs.lib`. + +The `/nt/primitives` directory should have no dependencies on NixTypes *(with one exception explained below)*. +Thus the **NixTypes system must be constructed from a dependency-free standard library.** + +This includes dependency on `this` (provided by the `nt.mix` module system)! +`/nt/primitives` is structured as a `nt.mix` module, and hence `/nt/primitives/mix` +must also depend on `/nt/primitives/std`. My point is, **`/nt/primitives/std` +cannot use mix at all!** + +### Internal Use Only +**None of these functions are exported for users of NixTypes!** So they should +remain as simple and minimal as possible to avoid extra work maintaining. +Instead, **all of these functions will be reimplemented** post-bootstrap +to be NixType compatible. diff --git a/nt/primitives/std/attrs.nix b/nt/primitives/std/attrs.nix new file mode 100644 index 0000000..ab58037 --- /dev/null +++ b/nt/primitives/std/attrs.nix @@ -0,0 +1,73 @@ +{...}: let + inherit + (builtins) + attrNames + elem + elemAt + filter + hasAttr + head + length + mapAttrs + partition + removeAttrs + tail + typeOf + ; +in rec { + enfIsAttrs = value: msg: let + got = typeOf value; + in + got == "set" || throw "${msg}: expected primitive nix type \"set\" but got \"${got}\""; + + # NOTE: doesn't check if xs is type set, use enfHasAttr instead + enfHasAttrUnsafe = name: xs: msg: + hasAttr name xs || throw "${msg}: missing required attribute \"${name}\""; + + # NOTE: use enfHasAttr' if you can guarantee xs is type set + enfHasAttr = name: xs: msg: + enfIsAttrs xs msg && enfHasAttrUnsafe name xs msg; + + mergeAttrsList = list: let + # `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end` + # Type: Int -> Int -> Attrs + binaryMerge = start: end: + # assert start < end; # Invariant + if end - start >= 2 + then + # If there's at least 2 elements, split the range in two, recurse on each part and merge the result + # The invariant is satisfied because each half will have at least 1 element + binaryMerge start (start + (end - start) / 2) // binaryMerge (start + (end - start) / 2) end + else + # Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly + elemAt list start; + in + if list == [] + then + # Calling binaryMerge as below would not satisfy its invariant + {} + else binaryMerge 0 (length list); + + removeAttrsRec = paths: xs: let + parts = partition (p: length p == 1) paths; + here = parts.right; + next = parts.wrong; + in + xs + |> flipCurry removeAttrs here + |> mapAttrs (name: + if ! elem name next + then id + else + next + |> filter (x: head x == name) + |> map tail + |> removeAttrsRec); + + filterAttrs = pred: xs: + attrNames xs + |> filter (name: ! pred name xs.${name}) + |> removeAttrs xs; + + nameValuePair = name: value: {inherit name value;}; +} diff --git a/nt/primitives/std/default.nix b/nt/primitives/std/default.nix new file mode 100644 index 0000000..130c293 --- /dev/null +++ b/nt/primitives/std/default.nix @@ -0,0 +1,28 @@ +# {mix, ...} @ inputs: +# mix.newMixture inputs (mixture: { +# includes.public = [ +# ./attrs.nix +# ./fn.nix +# ./list.nix +# ./num.nix +# ./string.nix +# ]; +# }) +# WARNING: /nt/primitives/std cannot depend on mix +# WARNING: this file is strictly for bootstrapping nt +let + # input = {inherit this;}; + this = { + }; + # this = + # import ./util.nix input + # // import ./parse.nix input + # // import ./trapdoor.nix input + # // import ./null.nix input + # // import ./maybe.nix input + # // import ./wrap.nix input + # // import ./enforce.nix input + # // import ./sig.nix input + # // import ./nt.nix input; +in + this diff --git a/nt/primitives/std/enforce.nix b/nt/primitives/std/enforce.nix new file mode 100644 index 0000000..ea8f50d --- /dev/null +++ b/nt/primitives/std/enforce.nix @@ -0,0 +1,2 @@ +{...}: { +} diff --git a/nt/primitives/std/fn.nix b/nt/primitives/std/fn.nix new file mode 100644 index 0000000..5c6014c --- /dev/null +++ b/nt/primitives/std/fn.nix @@ -0,0 +1,10 @@ +{...}: { + id = x: x; + flipCurry = f: a: b: f b a; + + # not sure where else to put this... + nullOr = f: x: + if x != null + then f x + else x; +} diff --git a/nt/primitives/std/list.nix b/nt/primitives/std/list.nix new file mode 100644 index 0000000..d406671 --- /dev/null +++ b/nt/primitives/std/list.nix @@ -0,0 +1,71 @@ +{...}: let + inherit + (builtins) + elemAt + foldl' + genList + length + ; +in rec { + sublist = start: count: list: let + len = length list; + in + genList (n: elemAt list (n + start)) ( + if start >= len + then 0 + else if start + count > len + then len - start + else count + ); + + take = count: sublist 0 count; + + init = list: + assert (list != []) || throw "lists.init: list must not be empty!"; + take (length list - 1) list; + + last = list: + assert (list != []) || throw "lists.last: list must not be empty!"; + elemAt list (length list - 1); + + # REF: pkgs.lib.lists.reverseList + reverse = xs: let + l = length xs; + in + genList (n: elemAt xs (l - n - 1)) l; + + # REF: pkgs.lib.lists.foldr + foldr = op: nul: list: let + len = length list; + fold' = n: + if n == len + then nul + else op (elemAt list n) (fold' (n + 1)); + in + fold' 0; + + # REF: pkgs.lib.lists.findFirstIndex [MODIFIED] + firstIndexOf = x: list: let + resultIndex = + foldl' ( + index: el: + if index < 0 + then + # No match yet before the current index, we need to check the element + if el == x + then + # We have a match! Turn it into the actual index to prevent future iterations from modifying it + -index - 1 + else + # Still no match, update the index to the next element (we're counting down, so minus one) + index - 1 + else + # There's already a match, propagate the index without evaluating anything + index + ) (-1) + list; + in + if resultIndex < 0 + then null + else resultIndex; +} diff --git a/nt/primitives/std/num.nix b/nt/primitives/std/num.nix new file mode 100644 index 0000000..eb36537 --- /dev/null +++ b/nt/primitives/std/num.nix @@ -0,0 +1,30 @@ +{...}: let + inherit + (builtins) + elemAt + genList + length + ; +in rec { + inc = x: x + 1; + dec = x: x - 1; + + countEvensLeq = n: n / 2; + countOddsLeq = n: (n + 1) / 2; + + nats = genList (x: x); + + odds = genList (x: 2 * x + 1); + oddsLeq = n: countOddsLeq n |> genList (x: 2 * x + 1); + + evens = genList (x: 2 * x); + evensLeq = n: countEvensLeq n |> genList (x: 2 * x); + + # WARNING: mapOdd/mapEven assuming the indexing set begins even (ie start counting from 0) + mapOdd = f: list: oddsLeq (length list - 1) |> map (i: f (elemAt list i)); + mapEven = f: list: evensLeq (length list + 1) |> map (i: f (elemAt list i)); + + # WARNING: filterOdd/filterEven assuming the indexing set begins even (ie start counting from 0) + filterOdd = mapOdd (x: x); + filterEven = mapEven (x: x); +} diff --git a/nt/primitives/std/string.nix b/nt/primitives/std/string.nix new file mode 100644 index 0000000..6a56b21 --- /dev/null +++ b/nt/primitives/std/string.nix @@ -0,0 +1,25 @@ +{...}: let + inherit + (builtins) + genList + match + replaceStrings + stringLength + substring + ; +in rec { + stringElem = i: substring i 1; + stringTake = substring 0; + stringHead = stringTake 1; + stringTail = x: x |> substring 1 (stringLength x - 1); + stringInit = x: x |> stringTake 1; + stringLast = x: stringElem (stringLength x - 1); + + stringToCharacters = s: genList (p: substring p 1 s) (stringLength s); + + escape = list: replaceStrings list (map (c: "\\${c}") list); + escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); + + hasInfix = infix: content: + match ".*${escapeRegex infix}.*" "${content}" != null; +} diff --git a/nt/primitives/util/enforce.nix b/nt/primitives/util/enforce.nix index 5721ac5..3957634 100644 --- a/nt/primitives/util/enforce.nix +++ b/nt/primitives/util/enforce.nix @@ -1,7 +1,6 @@ {this, ...}: let inherit (builtins) - hasAttr typeOf ; @@ -13,20 +12,12 @@ isTypeSig toTypeSig ; -in rec { +in { enfIsType = type: value: msg: let got = typeOf value; in got == type || throw "${msg}: expected primitive nix type \"${type}\" but got \"${got}\""; - # NOTE: doesn't check if xs is type set, use enfHasAttr instead - enfHasAttr' = name: xs: msg: - hasAttr name xs || throw "${msg}: missing required attribute \"${name}\""; - - # NOTE: use enfHasAttr' if you can guarantee xs is type set - enfHasAttr = name: xs: msg: - enfIsType "set" xs msg && enfHasAttr' name xs msg; - enfIsClassSig = sig: msg: isClassSig sig || throw "${msg}: given value \"${toString sig}\" of primitive nix type \"${typeOf sig}\" is not a valid Typeclass signature"; @@ -36,8 +27,6 @@ in rec { enfIsNT = T: msg: isNT T || throw "${msg}: expected nt compatible type but got \"${toString T}\" of primitive nix type \"${typeOf T}\""; - # assert enfImpls "nt::&Maybe" T "nt::&Maybe.unwrap"; - # impls = type: T: assert enfIsNT T "nt.impls"; impls' type T; enfImpls = type: T: msg: impls type T || throw "${msg}: given type \"${toTypeSig T}\" does not implement typeclass \"${toTypeSig type}\""; } diff --git a/nt/primitives/util/util.nix b/nt/primitives/util/util.nix deleted file mode 100644 index 39cbda7..0000000 --- a/nt/primitives/util/util.nix +++ /dev/null @@ -1,172 +0,0 @@ -# TODO: move these declarations to a separate module (maybe?) -{...}: let - inherit - (builtins) - attrNames - elem - elemAt - filter - foldl' - head - genList - length - mapAttrs - match - partition - removeAttrs - replaceStrings - stringLength - substring - tail - ; -in rec { - id = x: x; - flipCurry = f: a: b: f b a; - - inc = x: x + 1; - dec = x: x - 1; - - sublist = start: count: list: let - len = length list; - in - genList (n: elemAt list (n + start)) ( - if start >= len - then 0 - else if start + count > len - then len - start - else count - ); - - take = count: sublist 0 count; - - init = list: - assert (list != []) || throw "lists.init: list must not be empty!"; - take (length list - 1) list; - - last = list: - assert (list != []) || throw "lists.last: list must not be empty!"; - elemAt list (length list - 1); - - # REF: pkgs.lib.lists.reverseList - reverse = xs: let - l = length xs; - in - genList (n: elemAt xs (l - n - 1)) l; - - # REF: pkgs.lib.lists.foldr - foldr = op: nul: list: let - len = length list; - fold' = n: - if n == len - then nul - else op (elemAt list n) (fold' (n + 1)); - in - fold' 0; - - # REF: pkgs.lib.lists.findFirstIndex [MODIFIED] - firstIndexOf = x: list: let - resultIndex = - foldl' ( - index: el: - if index < 0 - then - # No match yet before the current index, we need to check the element - if el == x - then - # We have a match! Turn it into the actual index to prevent future iterations from modifying it - -index - 1 - else - # Still no match, update the index to the next element (we're counting down, so minus one) - index - 1 - else - # There's already a match, propagate the index without evaluating anything - index - ) (-1) - list; - in - if resultIndex < 0 - then null - else resultIndex; - - nullOr = f: x: - if x != null - then f x - else x; - - stringElem = i: substring i 1; - stringTake = substring 0; - stringHead = stringTake 1; - stringTail = x: x |> substring 1 (stringLength x - 1); - stringInit = x: x |> stringTake 1; - stringLast = x: stringElem (stringLength x - 1); - - stringToCharacters = s: genList (p: substring p 1 s) (stringLength s); - - escape = list: replaceStrings list (map (c: "\\${c}") list); - escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); - - hasInfix = infix: content: - match ".*${escapeRegex infix}.*" "${content}" != null; - - countEvensLeq = n: n / 2; - countOddsLeq = n: (n + 1) / 2; - - nats = genList id; - - odds = genList (x: 2 * x + 1); - oddsLeq = n: countOddsLeq n |> genList (x: 2 * x + 1); - - evens = genList (x: 2 * x); - evensLeq = n: countEvensLeq n |> genList (x: 2 * x); - - # WARNING: mapOdd/mapEven assuming the indexing set begins even (ie start counting from 0) - mapOdd = f: list: oddsLeq (length list - 1) |> map (i: f (elemAt list i)); - mapEven = f: list: evensLeq (length list + 1) |> map (i: f (elemAt list i)); - - # WARNING: filterOdd/filterEven assuming the indexing set begins even (ie start counting from 0) - filterOdd = mapOdd id; - filterEven = mapEven id; - - mergeAttrsList = list: let - # `binaryMerge start end` merges the elements at indices `index` of `list` such that `start <= index < end` - # Type: Int -> Int -> Attrs - binaryMerge = start: end: - # assert start < end; # Invariant - if end - start >= 2 - then - # If there's at least 2 elements, split the range in two, recurse on each part and merge the result - # The invariant is satisfied because each half will have at least 1 element - binaryMerge start (start + (end - start) / 2) // binaryMerge (start + (end - start) / 2) end - else - # Otherwise there will be exactly 1 element due to the invariant, in which case we just return it directly - elemAt list start; - in - if list == [] - then - # Calling binaryMerge as below would not satisfy its invariant - {} - else binaryMerge 0 (length list); - - removeAttrsRec = paths: xs: let - parts = partition (p: length p == 1) paths; - here = parts.right; - next = parts.wrong; - in - xs - |> flipCurry removeAttrs here - |> mapAttrs (name: - if ! elem name next - then id - else - next - |> filter (x: head x == name) - |> map tail - |> removeAttrsRec); - - filterAttrs = pred: xs: - attrNames xs - |> filter (name: ! pred name xs.${name}) - |> removeAttrs xs; - - nameValuePair = name: value: {inherit name value;}; -}