holy mega commit

there's no way this runs :(
This commit is contained in:
Emile Clark-Boman 2026-01-24 19:02:20 +10:00
parent 6537f0cdaa
commit affeace641
12 changed files with 639 additions and 0 deletions

6
nt/default.nix Normal file
View file

@ -0,0 +1,6 @@
{mix, ...} @ inputs:
mix.newMixture inputs (mixture: {
includes.public = [
./primitives
];
})

9
nt/primitives/README.md Normal file
View file

@ -0,0 +1,9 @@
OVERRIDE ALL NIX BUILTINS
ie at the root of your flake you modify what builtins is / use an input
you should never have to depend on builtins or pkgs.lib, only nt
Naming Convention
MyName -> Type
MyName' -> Typeclass

10
nt/primitives/default.nix Normal file
View file

@ -0,0 +1,10 @@
{mix, ...} @ inputs:
mix.newMixture inputs (mixture: {
includes.public = [
./nt.nix
];
imports.public = [
# TODO: make ./util private
./util
];
})

104
nt/primitives/nt.nix Normal file
View file

@ -0,0 +1,104 @@
{this, ...}: let
inherit
(builtins)
isFunction
length
mapAttrs
partition
;
inherit
(this.util)
enfType
enfIsClassSig
flipCurry
hasAttrAt
mkTrapdoorSet
ntTrapdoorKey
parseClassSig
projectOnto
removeAttrsRec
typeSig
Wrap
;
recdef = def: let
Self = def Self;
in
Self;
classDecl = {
derive = Wrap [];
ops = Wrap {};
};
unwrapBuilder = builder: Self:
if isFunction builder
then builder Self
else builder;
parseDecl = base: decl:
assert enfType "parseDecl" "set" decl;
# ^^^^ "Type declaration must be provided as an attribute set, got "${typeOf decl}" instead!"
decl |> projectOnto base;
# Algorithm: given a full set of ops, iterate each op and
# IF IT MATCHES A DERIVE BY FULL NAMESPACE
# THEN remove it from state.req
# ELSE IF IT IS SPECIFIED BY NAMESPACE
# THEN add it to a list of all invalid ops (errors)
# ELSE add it to a list of ops belonging solely to self
parseOps = ops: req: let
reqPaths =
req
|> mapAttrs (name: let
segs = parseClassSig name;
in
value: segs ++ [value]);
# XXX: TODO: having to specify the full namespace sucks :(
matches = partition (flipCurry hasAttrAt ops) reqPaths;
pathsMissing = matches.wrong;
opsSelf = removeAttrsRec matches.right ops;
opsDerived = removeAttrsRec matches.wrong ops;
in {
inherit opsSelf opsDerived pathsMissing;
success = length pathsMissing == 0;
};
mkClass = sig: decl:
assert enfIsClassSig "mkClass" sig; let
allDerivedClasses =
decl.derive
|> map (class: typeSig class ++ class.${ntTrapdoorKey}.derive);
parseResult = parseOps decl.ops decl.req;
inherit
(parseResult)
opsSelf
opsDerived
;
in
# XXX: WARNING: classes currently *shouldn't* be able to inherit ops (i think?)
assert parseResult.success || throw "TODO";
mkTrapdoorSet {
default = opsSelf;
unlock = {
# TODO: rename derive to deriveSigs (EXCEPT in the classDecl)
${ntTrapdoorKey} = {
inherit sig;
derive = allDerivedClasses;
ops = {${sig} = opsSelf;} // opsDerived;
req = null; # XXX: TODO make it more advanced
};
};
};
in {
Class = sig: builder:
recdef (Self:
unwrapBuilder builder Self
|> parseDecl classDecl
|> mkClass sig);
}

View file

@ -0,0 +1,12 @@
{mix, ...} @ inputs:
mix.newMixture inputs (mixture: {
includes.public = [
./enforce.nix
./nt.nix
./parse.nix
./sig.nix
./trapdoor.nix
./util.nix
./wrap.nix
];
})

View file

@ -0,0 +1,36 @@
{this, ...}: let
inherit
(builtins)
hasAttr
typeOf
;
inherit
(this)
isClassSig
isNT
isTypeSig
;
in rec {
enfType = msg: type: value: 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' = msg: name: xs:
hasAttr name xs || throw "${msg}: missing required attribute \"${name}\"";
# NOTE: use enfHasAttr' if you can guarantee xs is type set
enfHasAttr = msg: name: xs:
enfType "set" xs msg && enfHasAttr' name xs msg;
enfIsClassSig = msg: sig:
isClassSig sig || throw "${msg}: given value \"${toString sig}\" of primitive nix type \"${typeOf sig}\" is not a valid Typeclass signature";
enfIsTypeSig = msg: sig:
isTypeSig sig || throw "${msg}: given value \"${toString sig}\" of primitive nix type \"${typeOf sig}\" is not a valid Type signature";
enfIsNT = msg: T:
isNT T || throw "${msg}: expected nt compatible type but got \"${toString T}\" of primitive nix type \"${typeOf T}\"";
}

59
nt/primitives/util/nt.nix Normal file
View file

@ -0,0 +1,59 @@
{this, ...}: let
inherit
(builtins)
all
attrNames
elem
isAttrs
;
inherit
(this)
enfIsNT
ntTrapdoorKey
openTrapdoorFn
toTypeSig
;
in rec {
# check if a value is an nt type/class
isNT = T: let
content = openTrapdoorFn ntTrapdoorKey T;
names = attrNames content;
in
isAttrs content
&& all (name: elem name names) ["sig" "derive" "ops" "req"];
isNixClass = T: let
content = openTrapdoorFn ntTrapdoorKey T;
in
isAttrs content
&& attrNames content == ["sig" "derive" "ops" "req"];
isNixType = T: let
content = openTrapdoorFn ntTrapdoorKey T;
in
isAttrs content
&& attrNames content == ["instance" "sig" "derive" "ops" "req"]
&& content.instance == false;
isNixTypeInstance = T: let
content = openTrapdoorFn ntTrapdoorKey T;
in
isAttrs content
&& attrNames content == ["instance" "sig" "derive" "ops" "req"]
&& content.instance == true;
# check if a type/class implements a signature
# NOTE: unsafe variant, use typeSig if you can't guarantee `isNT T` holds
impls' = type: T: elem (toTypeSig type) T.${ntTrapdoorKey}.derive;
# NOTE safe variant, use impls' if you can guarantee `isNT T` holds
impls = type: T: assert enfIsNT "nt.impls" T; impls' type T;
# check if a type/class implements a signature
# NOTE: unsafe variant, use `is` if you can't guarantee `isNT T` holds
is' = type: T: T.${ntTrapdoorKey}.sig == toTypeSig type;
# NOTE safe variant, use `is'` if you can guarantee `isNT T` holds
is = type: T: assert enfIsNT "nt.is" T; is' type T;
}

View file

@ -0,0 +1,58 @@
{this, ...}: let
inherit
(builtins)
foldl'
hasAttr
isAttrs
;
inherit
(this)
enfType
is
Wrap
;
in rec {
# form: getAttrAt :: list string -> set -> null | Wrap Any
# given path as a list of strings, return that value of an
# attribute set at that path
getAttrAt = path: xs:
assert enfType "set" xs "getAttrAt";
foldl' (left: right:
if left != null && isAttrs left.value && hasAttr right left.value
then Wrap left.value.${right}
else null)
(Wrap xs)
path;
# form: hasAttrAt :: list string -> set -> bool
# given path as a list of strings, return that value of an
# attribute set at that path
hasAttrAt = path: xs:
assert enfType "set" xs "hasAttrAt";
getAttrAt path xs != null; # NOTE: inefficient (im lazy)
# Alternative to mapAttrsRecursiveCond
# Allows mapping directly from a child path
recmap = let
recmapFrom = path: f: T:
if builtins.isAttrs T && ! is Wrap T
then builtins.mapAttrs (attr: leaf: recmapFrom (path ++ [attr]) f leaf) T
else f path T;
in
recmapFrom [];
projectOnto = f: dst: src:
dst
|> recmap
(path: dstLeaf: let
srcLeaf = getAttrAt path src;
newLeaf =
if srcLeaf != null
then srcLeaf
else dstLeaf;
in
if is Wrap newLeaf
then newLeaf.value
else newLeaf);
}

103
nt/primitives/util/sig.nix Normal file
View file

@ -0,0 +1,103 @@
{this, ...}: let
inherit
(builtins)
isString
split
stringLength
;
inherit
(this)
enfIsNT
filterEven
init
last
ntTrapdoorKey
nullOr
stringHead
stringTail
;
in rec {
parseSig = sig: let
result = split "::" sig |> filterEven;
in
if last result == ""
then null
else result;
isTypeName = name: stringHead name != "&";
isClassName = name: stringHead name == "&" && stringLength name != 1;
isTypeSig = sig:
parseSig sig |> nullOr (result: last result |> isTypeName);
isClassSig = sig:
parseSig sig |> nullOr (result: last result |> isClassSig);
parseTypeSig = sig: let
result = parseSig sig;
in
if result != null && (last result |> isTypeName)
then result
else null;
parseClassSig = sig: let
result = parseSig sig;
in
if result != null && (last result |> isClassName)
then init result ++ (last result |> stringTail)
else null;
# NOTE: unsafe variant, use typeSig if you can't guarantee `isNT T` holds
typeSig' = T: T.${ntTrapdoorKey}.sig;
# NOTE: safe variant, use typeSig' if you can guarantee `isNT T` holds
typeSig = T: assert enfIsNT "nt.typeSig" T; typeSig' T;
toTypeSig = x:
if isString x
then x
else typeSig x;
# NOTE: we're testing how similar `list` is to `toTypeSig type` (non-commutative)
# NOTE: we measure similarity in the reverse order (ie end of signature is most important)
# sigSimilarity = type: list: let
# # XXX: TODO: mkClass must enforce that type names can't begin with &
# trimClassPrefix = sig:
# if stringHead sig == "&"
# then stringTail sig
# else sig;
# S = toTypeSig type |> parseTypeSig |> map trimClassPrefix;
# progress = l: x: let
# index = firstIndexOf x l;
# in
# if index == null
# then []
# else sublist index (length l - index) l;
# op = state: el: let
# acc' = progress state.acc el;
# in
# # Continue progression in healthy condition
# if state.acc != [] && acc' != []
# then {
# score = state.score + 1;
# acc = acc';
# }
# # We didn't match the final element (ABORT with score=0)
# else if state.score == 0
# then {
# score = 0;
# acc = [];
# }
# # We didn't match this element but maybe next time :)
# else state;
# in
# list
# |> foldr op {
# score = 0;
# acc = reverse S;
# }
# |> getAttr "score";
}

View file

@ -0,0 +1,60 @@
{this, ...}: let
inherit
(builtins)
attrNames
elem
isFunction
;
inherit
(this)
enfHasAttr
enfHasAttr'
enfIsType
;
masterkey = "_''traps''_";
in rec {
defaultTrapdoorKey = "_'";
mkTrapdoorKey = id: "${defaultTrapdoorKey}${id}";
ntTrapdoorKey = mkTrapdoorKey "nt";
mkTrapdoorFn = key: decl:
assert enfHasAttr "default" decl "mkTrapdoorFn";
assert enfHasAttr' "unlock" decl "mkTrapdoorFn";
# return trapdoor function
(x: let
keys = attrNames decl.unlock;
in
if elem key keys
then decl.unlock.${key}
else if key == masterkey
then keys
else decl.default);
mkTrapdoorSet = key: decl:
assert enfHasAttr "default" decl "mkTrapdoorSet";
assert enfHasAttr' "unlock" decl "mkTrapdoorSet";
# return trapdoor set
let
keys = attrNames decl.unlock;
in
decl.default
// {
${key} = decl.unlock.${key};
${masterkey} = keys;
};
revealTrapdoors = openTrapdoor masterkey;
openTrapdoorFn = key: f: f key;
openTrapdoorSet = key: xs: xs.${key};
# TODO: implement a function called enfIsTypeAny (for cases like this where it might be function or set)
openTrapdoor = key: T:
if isFunction T
then openTrapdoorFn key T
else
assert enfIsType "set" T "openTrapdoor";
openTrapdoorSet key T;
}

154
nt/primitives/util/util.nix Normal file
View file

@ -0,0 +1,154 @@
# TODO: move these declarations to a separate module (maybe?)
{...}: let
inherit
(builtins)
elem
elemAt
filter
foldl'
head
genList
length
mapAttrs
partition
removeAttrs
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);
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);
}

View file

@ -0,0 +1,28 @@
{this, ...}: let
inherit
(this)
ntTrapdoorKey
mkTrapdoorFn
mkTrapdoorSet
;
in {
# NOTE: Wrap is used to simplify parsing Type/Class declarations
# NOTE: and therefore must be implemented manually
Wrap = let
meta = instance: {
inherit instance;
sig = "nt::Wrap";
derive = [];
ops = {};
req = {};
};
in
mkTrapdoorFn ntTrapdoorKey {
default = value:
mkTrapdoorSet ntTrapdoorKey {
default = {inherit value;};
unlock = meta true;
};
unlock = meta false;
};
}