776 lines
25 KiB
Nix
776 lines
25 KiB
Nix
{ debug, derivation, expression, integer, intrinsics, library, list, set, type, ... } @ libs:
|
|
let
|
|
veryDeep
|
|
= {
|
|
depth = 10;
|
|
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}\"";
|
|
|
|
from'#: T -> string
|
|
# where T: Any
|
|
= { legacy, display, depth, maxDepth, nice, showType, trace, attrPath ? [], typo ? null } @ args:
|
|
input:
|
|
(
|
|
if trace
|
|
then
|
|
intrinsics.trace
|
|
(
|
|
let
|
|
attrPath'
|
|
= concatMapped
|
|
(
|
|
key:
|
|
if integer.isInstanceOf key
|
|
then
|
|
"[${integer.toString key}]"
|
|
else
|
|
".${key}"
|
|
)
|
|
attrPath;
|
|
in
|
|
"${if typo != null then "<${typo}>" else ""}(${if display then "debug" else "string"})${attrPath'}"
|
|
)
|
|
else
|
|
x: x
|
|
)
|
|
(
|
|
let
|
|
inherit(expression.tryEval input) success value;
|
|
in
|
|
if maxDepth != null
|
|
&& depth >= maxDepth
|
|
then
|
|
if !showType
|
|
then
|
|
"…"
|
|
else if !success
|
|
then
|
|
"<panic>"
|
|
else
|
|
type.matchPrimitiveOrDefault value
|
|
{
|
|
bool = if value then "true" else "false";
|
|
float = intrinsics.toString value;
|
|
int = integer.toString value;
|
|
}
|
|
(type.format value)
|
|
else if depth >= veryDeep.depth
|
|
then
|
|
debug.panic
|
|
"from'"
|
|
{
|
|
text = "Very deep o.O";
|
|
when = veryDeep.panic;
|
|
data
|
|
= concatMapped
|
|
(
|
|
key:
|
|
if integer.isInstanceOf key
|
|
then
|
|
"[${integer.toString key}]"
|
|
else
|
|
".${key}"
|
|
)
|
|
attrPath;
|
|
}
|
|
"<very deep (over ${toString depth})>"
|
|
else if !success
|
|
then
|
|
debug.panic
|
|
"from'"
|
|
{
|
|
text = "Panic occured while evaluation input value.";
|
|
when = !display || legacy;
|
|
}
|
|
"<panic>"
|
|
else
|
|
type.matchPrimitive value
|
|
{
|
|
bool
|
|
= if legacy
|
|
then
|
|
if value
|
|
then
|
|
"1"
|
|
else
|
|
""
|
|
else
|
|
if value
|
|
then
|
|
"true"
|
|
else
|
|
"false";
|
|
float = intrinsics.toString value;
|
|
int = integer.toString value;
|
|
lambda
|
|
= if display
|
|
then
|
|
if legacy
|
|
then
|
|
"<CODE>"
|
|
else
|
|
let
|
|
mapArguments
|
|
= { ... } @ data:
|
|
set.mapToList
|
|
(
|
|
key:
|
|
value:
|
|
if value
|
|
then
|
|
"${escapeKey key}?"
|
|
else
|
|
escapeKey key
|
|
)
|
|
data;
|
|
arguments = intrinsics.functionArgs value;
|
|
in
|
|
if arguments != {}
|
|
then
|
|
"{ ${concatWith ", " (mapArguments arguments)} }: …"
|
|
else
|
|
"_: …"
|
|
else
|
|
debug.panic "from'" "cannot coerse a function to a string";
|
|
list
|
|
= if value == []
|
|
then
|
|
if legacy
|
|
then
|
|
""
|
|
else
|
|
"[]"
|
|
else
|
|
let
|
|
indent = repeat " " depth;
|
|
body
|
|
= list.imap
|
|
(
|
|
index:
|
|
value:
|
|
let
|
|
value'
|
|
= from'
|
|
{
|
|
inherit display legacy maxDepth nice showType trace typo;
|
|
depth = depth + 1;
|
|
attrPath = attrPath ++ [ index ];
|
|
}
|
|
value;
|
|
in
|
|
if this != value
|
|
then
|
|
"${value'}"
|
|
else
|
|
"<repeating>"
|
|
)
|
|
value;
|
|
this = value;
|
|
in
|
|
if legacy
|
|
then
|
|
"[ ${concatMappedWith (value: "(${value})") " " body} ]"
|
|
else if nice
|
|
then
|
|
"[\n${indent} ${concatWith ",\n${indent} " body}\n${indent}]"
|
|
else
|
|
"[ ${concatWith ", " body} ]";
|
|
null
|
|
= if legacy
|
|
then
|
|
""
|
|
else
|
|
"null";
|
|
path = toString value;#"${value}";
|
|
set
|
|
= if value == {}
|
|
then
|
|
"{}"
|
|
else if display
|
|
then
|
|
let
|
|
body
|
|
= set.mapToList format
|
|
(
|
|
if value.__public__ or null != null
|
|
then
|
|
set.filterByName
|
|
value
|
|
value.__public__
|
|
else
|
|
set.remove
|
|
value
|
|
[ "__public__" "__type__" "__variant__" ]
|
|
);
|
|
format
|
|
= key:
|
|
value:
|
|
let
|
|
key' = escapeKey key;
|
|
value'
|
|
= from'
|
|
{
|
|
inherit legacy display nice showType;
|
|
maxDepth
|
|
= if !hasUnfix
|
|
then
|
|
maxDepth
|
|
else
|
|
depth;
|
|
trace
|
|
= trace
|
|
|| (key' == "lib");
|
|
depth = depth + 1;
|
|
attrPath = attrPath ++ [ key' ];
|
|
typo
|
|
= if type.getType value != null
|
|
then
|
|
typeName
|
|
else
|
|
typo;
|
|
}
|
|
value;
|
|
in
|
|
if key' != "lib"
|
|
then
|
|
if value != this
|
|
then
|
|
"${key'} = ${value'};"
|
|
else
|
|
"${key'} = <repeating>;"
|
|
else
|
|
"<lib>";
|
|
hasUnfix = value ? __unfix__;
|
|
indent = repeat " " depth;
|
|
niceText = "{\n${indent} ${concatWith "\n${indent} " body}\n${indent}}";
|
|
this = value;
|
|
typeName = type.format value;
|
|
in
|
|
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 DoNotFollow.isInstanceOf value
|
|
then
|
|
"<skipped>"
|
|
else if value._type or null != null
|
|
then
|
|
"<${value._type}>"
|
|
else if nice && !legacy
|
|
then
|
|
if type.getType value != null
|
|
then
|
|
"<${typeName} ${niceText}>"
|
|
else
|
|
niceText
|
|
else if legacy
|
|
|| type.getType value == null
|
|
then
|
|
"{ ${concatWith " " body} }"
|
|
else
|
|
"<${typeName} { ${concatWith " " body} }>"
|
|
else if value ? outPath
|
|
then
|
|
value.outPath
|
|
else if value ? __toString
|
|
then
|
|
value.__toString value
|
|
else
|
|
debug.panic "from'"
|
|
{
|
|
text = "cannot coerse this set to a string";
|
|
data = value;
|
|
};
|
|
string
|
|
= let
|
|
value' = escape value;
|
|
in
|
|
if nice
|
|
then
|
|
let
|
|
indent = repeat " " depth;
|
|
lines = splitLines value;
|
|
lines'
|
|
= concatMapped
|
|
(
|
|
line:
|
|
if match "[ \n\r\t]*" line != null
|
|
then
|
|
"\n${indent}"
|
|
else
|
|
"\n${indent} ${replace [ "\${" "''" ] [ "''\${" "'''" ] line}"
|
|
)
|
|
lines;
|
|
in
|
|
if list.length lines > 1
|
|
then
|
|
"''${lines'}''"
|
|
else
|
|
"\"${value'}\""
|
|
else if display || depth > 0
|
|
then
|
|
"\"${value'}\""
|
|
else
|
|
value;
|
|
}
|
|
);
|
|
|
|
from#: T -> string
|
|
# where T: Any
|
|
= from' { display = false; legacy = false; maxDepth = null; depth = 0; nice = false; showType = false; trace = false; };
|
|
|
|
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;
|
|
|
|
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 (
|
|
from' { display = false; legacy = true; maxDepth = null; depth = 0; nice = false; showType = false; trace = false; }
|
|
);
|
|
|
|
toTrace#: T -> string
|
|
# where T: Any
|
|
= maxDepth: nice: showType: trace: from' { display = true; legacy = false; inherit maxDepth nice showType trace; depth = 0; };
|
|
|
|
toTraceDeep#: T -> string
|
|
# where T: Any
|
|
= toTrace null;
|
|
|
|
toTraceShallow#: T -> string
|
|
# where T: Any
|
|
= toTrace 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 if 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 from'
|
|
getByte getChar getContext getFinalChar
|
|
hasContext hash
|
|
isEmpty isInstanceOf
|
|
length lengthUTF8
|
|
match
|
|
orNull
|
|
replace replace' repeat
|
|
slice split splitAt splitAt'
|
|
splitLines splitSpaces
|
|
toBytes toCharacters toLowerCase toPath toString toTrace toTraceDeep toTraceShallow toUpperCase toUTF8characters trim;
|
|
inherit(trim) ltrim ltrim' rtrim rtrim' trim';
|
|
|
|
isPrimitive = true;
|
|
}
|