Sebastian Walz 860d31cee1
Tohu vaBohu
2023-04-21 00:22:52 +02:00

740 lines
23 KiB
Nix

{ bool, debug, derivation, expression, integer, intrinsics, library, list, set, type, ... } @ libs:
let
veryDeep#: { depth: int, panic: bool }
= {
depth = 64;
panic = false;
};
DoNotFollow
= type "DoNotFollow"
{
from = value: DoNotFollow.instanciate { inherit value; };
};
combine = list.combine (a: b: "${a}${b}");
hexChars = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "a" "b" "c" "d" "e" "f" ];
hexPairs = combine hexChars hexChars;
appendContext
= intrinsics.appendContext;
ascii = list.generate (index: getChar index) 128;
lowerAscii = list.generate (index: getChar ( 97 + index)) 26;
upperAscii = list.generate (index: getChar ( 65 + index)) 26;
char
= {
backspace = getChar' "0008";
carriageReturn = "\r";
delete = getChar' "007f";
escape = getChar' "001b";
horizontalTab = "\t";
lineFeed = "\n";
null = "";
};
concat#: [ string ] -> string
= concatWith "";
concatIndexMapped#: F -> [ T ] -> string
# where
# F: int -> T -> string,
# T: Any
= convert:
concatIndexMappedWith convert "";
concatIndexMappedWith#: F -> string -> [ T ] -> string
# where
# F: int -> T -> string,
# T: Any:
= convert:
seperator:
parts:
concatWith seperator (list.imap convert parts);
concatLines#: [ string ] -> string
= concatWith "\n";
concatMapped#: F -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
concatMappedWith convert "";
concatMapped'#: F -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
concatMappedWith' convert "";
concatMappedLines#: F -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
concatMappedWith convert "\n";
concatMappedLines'#: F -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
concatMappedWith' convert "\n";
concatMappedWith#: F -> string -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
seperator:
parts:
concatWith seperator (list.map convert parts);
concatMappedWith'#: F -> string -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
seperator:
parts:
concatWith seperator (set.mapToList convert parts);
concatMappedWithFinal#: F -> string -> string -> [ T ] -> string
# where
# F: T -> string,
# T: Any
= convert:
seperator:
final:
parts:
concatWithFinal
seperator
final
(list.map convert parts);
concatMappedWithFinal'#: F -> string -> string -> [ T ] -> string
# where
# F: string -> T -> string,
# T: Any
= convert:
seperator:
final:
parts:
concatWithFinal
seperator
final
(set.mapToList convert parts);
concatWith#: string -> [ string ] -> string
= intrinsics.concatStringsSep
or (
seperator:
parts:
list.fold
(
result:
entry:
"${result}${seperator}${entry}"
)
(list.head parts)
(list.tail parts)
);
concatWithFinal#: string -> string -> [ string ] -> string
= seperator:
final:
parts:
if list.length parts > 1
then
"${concatWith seperator (list.body parts)}${final}${list.foot parts}"
else
list.head parts;
concatWords#: [ string ] -> string
= concatWith " ";
discardContext
= intrinsics.unsafeDiscardStringContext;
discardOutputDependency
= intrinsics.unsafeDiscardOutputDependency;
escape
= replace
[ "\"" "\\" "\n" "\r" "\t" char.escape ]
[ "\\\"" "\\\\" "\\n" "\\r" "\\t" "\\e" ];
escapeKey
= key:
if (match "[A-Za-z_][-'0-9A-Za-z_]*" key) != null
then
key
else
"\"${escape key}\"";
format#: T -> string
# where T: Any
= {
display ? false,
hex ? false,
legacy ? false,
maxDepth ? null,
nice ? false,
showType ? false,
trace ? false,
}:
formatValue
{
inherit display hex legacy maxDepth nice showType trace;
attrPath = [];
depth = 0;
indent = "";
seen = [];
};
formatAttrPath
= concatMapped
(
key:
bool.select
(integer.isInstanceOf key)
"[${integer.toString key}]"
".${key}"
);
formatBool = { legacy, ... }: (bool.select legacy bool.formatLegacy bool.format);
formatFloat = { ... }: intrinsics.toString;
formatInteger = { hex, ... }: bool.select hex integer.formatHexaDecimal integer.toString;
formatLambda
= let
mapArgument
= key:
value:
"${escapeKey key}${bool.select value "?" ""}";
mapArguments = set.mapToList mapArgument;
in
{ depth, display, maxDepth, legacy, ... }:
value:
let
args = intrinsics.functionArgs value;
value'
= bool.select (args != {})
"{ ${concatWith ", " (mapArguments args)} }: ..."
"_: ...";
in
if depth == maxDepth
then
"_: ..."
else if display
then
bool.select legacy "<CODE>" value'
else
debug.panic [ "formatLambda" ]
{
text = "cannot coerse a function to a string";
data = value';
};
formatList
= { depth, indent, legacy, maxDepth, nice, ... } @ env:
value:
let
body = list.imap (formatValue' env) value;
in
if depth == maxDepth then "[ ... ]"
else if value == [] then bool.select legacy "" "[]"
else if legacy then "[ ${concatMappedWith (value: "(${value})") " " body} ]"
else if nice then "[\n${indent} ${concatWith ",\n${indent} " body}\n${indent}]"
else "[ ${concatWith ", " body} ]";
formatNull = { legacy, ... }: _: bool.select legacy "" "null";
formatPath
= { depth, maxDepth, ... }:
value:
if depth == maxDepth then "<path>"
else intrinsics.toString value; #"${value}";
formatSet
= { depth, display, indent, legacy, maxDepth, nice, ... } @ env:
{ ... } @ value:
let
body
= set.mapToList format
(
bool.select
(value.__public__ or null != null)
(set.filterByName value value.__public__)
(set.remove value [ "__public__" "__type__" "__variant__" ])
);
format
= key:
value:
let
key' = escapeKey key;
in
"${key'} = ${formatValue' env key' value};";
niceText = "{\n${indent} ${concatWith "\n${indent} " body}\n${indent}}";
niceText'
= if body != []
then
bool.select
(type.getType value != null)
"<${typeName} ${niceText}>"
niceText
else
"{}";
typeName = type.format value;
value'
= if debug.Debug.isInstanceOf value then "<Debug>"
else if derivation.isInstanceOf' value then "<Derivation ${value.name}>"
else if library.isInstanceOf value then "<Library ${value.getVariant}>"
else if value._type or null != null then "<${value._type}>"
else if nice && !legacy then niceText'
else if legacy || type.getType value == null then "{ ${concatWith " " (set.mapToList format value)} }"
else if body != [] then "<${typeName} { ${concatWith " " body} }>"
else "<${typeName}>";
in
if depth == maxDepth then "{ ... }"
else if value == {} then "{}"
else if display then value'
else if value ? outPath then value.outPath
else if value ? __toString then value.__toString value
else
debug.panic "formatSet"
{
text = "cannot coerse this set to a string";
data = value;
};
formatString
= { depth, display, indent, maxDepth, nice, ... }:
value:
let
value' = escape value;
in
if depth == maxDepth
then
"<string>"
else if nice
then
let
lines = splitLines value;
lines'
= concatMapped
(
line:
bool.select
(match "[ \n\r\t]*" line != null)
"\n${indent}"
"\n${indent} ${replace [ "\${" "''" ] [ "''\${" "'''" ] line}"
)
lines;
in
if list.length lines > 1 then "''${lines'}''"
else "\"${value'}\""
else if display || depth > 0 then "\"${value'}\""
else value;
formatValue
= { attrPath, depth, display, legacy, maxDepth, seen, showType, trace, ... } @ env:
input:
let
env'
= env
// {
seen = seen ++ [ value ];
};
format
= type.matchPrimitive value
{
bool = formatBool;
float = formatFloat;
int = formatInteger;
lambda = formatLambda;
list = formatList;
null = formatNull;
path = formatPath;
set = formatSet;
string = formatString;
};
doNotFormat
= !showType
&& maxDepth != null
&& depth >= maxDepth;
valueIfUnsuccessful
= debug.panic [ "formatValue" "valueIfUnsuccessful" ]
{
text = "Panic occured while evaluation input value.";
when = !display || legacy;
}
"<panic>";
valueIfVeryDeep
= debug.panic [ "formatValue" "valueIfVeryDeep" ]
{
text = "Very deep o.O";
when = veryDeep.panic;
data = formatAttrPath attrPath;
}
"<very deep (over ${formatInteger env' depth})>";
inherit(expression.tryEval input) success value;
value'
= if doNotFormat then "..."
else if !success then valueIfUnsuccessful
else if list.find value seen then "<recursion>"
else if depth >= veryDeep.depth then valueIfVeryDeep
else format env' value;
in
if trace
then
intrinsics.trace "<$>${formatAttrPath attrPath}" value'
else
value';
formatValue'
= { attrPath, depth, indent, seen, ... } @ env:
key:
formatValue
(
env
// {
attrPath = attrPath ++ [ key ];
depth = depth + 1;
indent = "${indent} ";
}
);
from = format {};
getByte#: string -> int
= text: getByte' (slice 0 1 text);
getByte'#: string -> int
= let
get#: string -> int -> string
= text: index: slice index 1 text;
head#: string -> char
= text: get text 0;
bytes
= intrinsics.listToAttrs
(
( list.generate ( value: { name = getChar value; inherit value; } 127 ) )
++ [
{ name = head ( getChar' "0080" ); value = 194; }
{ name = head ( getChar' "00c0" ); value = 195; }
]
++ (
list.combine
(
a: b:
{
name = head ( getChar' "0${list.get hexChars a}${list.get hexChars (4 * b)}0" );
value = 192 + 4 * a + b;
}
)
( list.range 1 7 )
( list.range 0 3 )
)
++ (
list.map
(
a:
{
name = head ( getChar' "${list.get hexChars a}800" );
value = 224 + a;
}
)
( list.range 0 15 )
)
++ (
list.combine
(
a:
b:
{
name = get ( getChar' "00${list.get hexChars (8 + a)}${list.get hexChars b}" ) 1;
value = 128 + 16 * a + b;
}
)
( list.range 0 3 )
( list.range 0 15 )
)
);
in
bytes.${char};
getChar'#: string -> char
= index: expression.fromJSON "\"\\u${index}\"";
getChar#: int -> char
= index: list.get ( list.map getChar' (combine hexPairs hexPairs) ) index;
getContext
= intrinsics.getContext;
getFinalChar
= self:
slice ((length self) - 1) 1 self;
hasContext
= intrinsics.hasContext;
hash#: string -> string -> string
= intrinsics.hashString;
ifOrEmpty
= condition:
text:
if condition
then
text
else
"";
isEmpty#: string -> bool
= text: text == "";
isInstanceOf = intrinsics.isString or (value: type.getPrimitive value == "string");
length#: string -> int
= intrinsics.stringLength
or (
text:
let
rest = slice 1 9223372036854775807 text;
in
if text == "" then 0
else ( length rest ) + 1
);
lengthUTF8#: string -> int
= text: list.length ( toUTF8characters text );
match#: string -> string -> [ T ]
# where T: null | string | [ T ]
= intrinsics.match;
orNull
= value:
isInstanceOf value || value == null;
repeat#: string -> int -> string
= text:
multiplier:
concat ( list.generate (_: text) multiplier );
replace#: [ string ] -> [ string ] -> string -> string
= intrinsics.replaceStrings; /* should be possible to construct, but ahhh */
replace'
= { ... } @ substitutions:
replace
(set.names substitutions)
(set.values substitutions);
slice#: int -> int -> string -> string
= intrinsics.substring;
split#: string -> string -> [ T ]
# where T: null | string | [ T ]
= intrinsics.split;
splitAt#: string -> string -> [ string ]
= regex:
text:
list.filter
( line: isInstanceOf line )
( split regex text );
splitAt'#: string -> string -> [ string ]
= regex:
text:
list.filter
( line: isInstanceOf line && line != "")
( split regex text );
splitLines#: string -> [ string ]
= splitAt "\n";
splitSpaces
= splitAt "([[:space:]]+)";
splitTabs#: string -> [ string ]
= splitAt "\t";
toBytes#: string -> [ u8 ]
= text: list.map getByte' (toCharacters text);
toCharacters#: string -> [ asciiChar ]
= text:
list.generate (index: slice index 1 text) (length text);
toPath#: string -> path
= path: "./${path}";
toLowerCase#: string -> string
= let
caseMap
= set.pair
( upperAscii ++ [ "Ä" "Ö" "Ü" "" ] )
( lowerAscii ++ [ "ä" "ö" "ü" "ß" ] );
in
text:
list.fold
(
text:
char:
"${text}${caseMap.${char} or char}"
)
""
( toUTF8characters text );
toString#: T -> string
= intrinsics.toString
or (
format
{
display = false;
hex = false;
legacy = true;
maxDepth = null;
nice = false;
showType = false;
trace = false;
}
);
toTrace#: T -> string
# where T: Any
= { hex, maxDepth, nice, showType, trace }:
format
{
inherit hex maxDepth nice showType trace;
display = true;
legacy = false;
};
toTraceDeep#: T -> string
# where T: Any
= { hex, nice, showType, trace }:
format
{
inherit hex nice showType trace;
display = true;
legacy = false;
maxDepth = null;
};
toTraceShallow#: T -> string
# where T: Any
= { hex, nice, showType, trace }:
format
{
inherit hex nice showType trace;
display = true;
legacy = false;
maxDepth = 1;
};
toUpperCase#: string -> string
= let
# The german letter ß (sz) cannot be at the start of a word and
# therefore does not have a capital form.
# However in uppercase text, the letter ẞ is allowed,
# but the unicode standard still defaults to SS.
# I do not care about that, I prefer ẞ instead.
caseMap
= set.pair
( lowerAscii ++ [ "ä" "ö" "ü" "ß" ] )
( upperAscii ++ [ "Ä" "Ö" "Ü" "" ] );
in
text:
concatMapped
(char: "${caseMap.${char} or char}")
(toUTF8characters text);
toUTF8characters#: string -> [ utf8char ]
= text:
let
this
= list.fold
(
{ result, text }:
character:
if character <= char.delete
then
{
text = "";
result
= result
++ ( if text != "" then [ text ] else [ ] )
++ [ character ];
}
else
{
# Does not validate!
# E.g. C2 A3 A3 would be considered one char,
# even though this is invalid utf8!
text = "${text}${character}";
inherit result;
}
)
{
text = "";
result = [ ];
}
(toCharacters text);
in
this.result
++ (
if this.text != ""
then
[ this.text ]
else
[ ]
);
trim = library.import ./trim.nix libs;
in
type "string"
{
inherit DoNotFollow;
inherit appendContext ascii
char
concat concatLines
concatIndexMapped concatIndexMappedWith
concatMapped concatMapped'
concatMappedLines concatMappedLines'
concatMappedWith concatMappedWith'
concatMappedWithFinal concatMappedWithFinal'
concatWith concatWithFinal concatWords
discardContext discardOutputDependency
escapeKey
from
getByte getChar getContext getFinalChar
hasContext hash
ifOrEmpty isEmpty isInstanceOf
length lengthUTF8
match
orNull
replace replace' repeat
slice split splitAt splitAt'
splitLines splitSpaces splitTabs
toBytes toCharacters toLowerCase toPath toString toTrace toTraceDeep toTraceShallow toUpperCase toUTF8characters trim;
inherit(trim) ltrim ltrim' rtrim rtrim' trim';
isPrimitive = true;
}