{ 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 "" 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; } "" else if !success then debug.panic "from'" { text = "Panic occured while evaluation input value."; when = !display || legacy; } "" 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 "" 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 "" ) 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'} = ;" else ""; 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 "" else if derivation.isInstanceOf' value then "" else if library.isInstanceOf value then "" else if DoNotFollow.isInstanceOf value then "" 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; }