740 lines
23 KiB
Nix
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;
|
|
}
|