use core::scalar use core::functions use core::error @description("The length of a string") @example("str_length(\"Numbat\")") fn str_length(s: String) -> Scalar @description("Subslice of a string") @example("str_slice(\"Numbat\", 3, 6)") fn str_slice(s: String, start: Scalar, end: Scalar) -> String @description("Get a single-character string from a Unicode code point.") @example("0x2764 -> chr") fn chr(n: Scalar) -> String @description("Get the Unicode code point of the first character in a string.") @example("\"❤\" -> ord") fn ord(s: String) -> Scalar @description("Convert a string to lowercase") @example("lowercase(\"Numbat\")") fn lowercase(s: String) -> String @description("Convert a string to uppercase") @example("uppercase(\"Numbat\")") fn uppercase(s: String) -> String @description("Concatenate two strings") @example("str_append(\"Numbat\", \"!\")") fn str_append(a: String, b: String) -> String = "{a}{b}" @description("Find the first occurrence of a substring in a string") @example("str_find(\"Numbat is a statically typed programming language.\", \"typed\")") fn str_find(haystack: String, needle: String) -> Scalar = if len_haystack == 0 then -1 else if str_slice(haystack, 0, str_length(needle)) == needle then 0 else if str_find(tail_haystack, needle) == -1 then -1 else 1 + str_find(tail_haystack, needle) where len_haystack = str_length(haystack) and tail_haystack = str_slice(haystack, 1, len_haystack) @description("Check if a string contains a substring") @example("str_contains(\"Numbat is a statically typed programming language.\", \"typed\")") fn str_contains(haystack: String, needle: String) -> Bool = str_find(haystack, needle) != -1 @description("Replace all occurrences of a substring in a string") @example("str_replace(\"Numbat is a statically typed programming language.\", \"statically typed programming language\", \"scientific calculator\")") fn str_replace(s: String, pattern: String, replacement: String) -> String = if pattern == "" then s else if str_contains(s, pattern) then if str_slice(s, 0, str_length(pattern)) == pattern then str_append(replacement, str_replace(str_slice(s, str_length(pattern), str_length(s)), pattern, replacement)) else str_append(str_slice(s, 0, 1), str_replace(str_slice(s, 1, str_length(s)), pattern, replacement)) else s @description("Repeat the input string `n` times") @example("str_repeat(\"abc\", 4)") fn str_repeat(a: String, n: Scalar) -> String = if n > 0 then str_append(a, str_repeat(a, n - 1)) else "" fn _bin_digit(x: Scalar) -> String = chr(48 + mod(x, 2)) fn _oct_digit(x: Scalar) -> String = chr(48 + mod(x, 8)) fn _hex_digit(x: Scalar) -> String = if x_16 < 10 then chr(48 + x_16) else chr(97 + x_16 - 10) where x_16 = mod(x, 16) fn _digit_in_base(base: Scalar, x: Scalar) -> String = if base < 2 || base > 16 then error("base must be between 2 and 16") else if x_16 < 10 then chr(48 + x_16) else chr(97 + x_16 - 10) where x_16 = mod(x, 16) # TODO: once we have anonymous functions / closures, we can implement base in a way # that it returns a partially-applied version of '_number_in_base'. This would allow # arbitrary 'x -> base(b)' conversions. @description("Convert a number to the given base.") @example("42 |> base(16)") fn base(b: Scalar, x: Scalar) -> String = if x < 0 then "-{base(b, -x)}" else if x < b then _digit_in_base(b, x) else str_append(base(b, floor(x / b)), _digit_in_base(b, mod(x, b))) @description("Get a binary representation of a number.") @example("42 -> bin") fn bin(x: Scalar) -> String = if x < 0 then "-{bin(-x)}" else "0b{base(2, x)}" @description("Get an octal representation of a number.") @example("42 -> oct") fn oct(x: Scalar) -> String = if x < 0 then "-{oct(-x)}" else "0o{base(8, x)}" @description("Get a decimal representation of a number.") @example("0b111 -> dec") fn dec(x: Scalar) -> String = base(10, x) @description("Get a hexadecimal representation of a number.") @example("2^31-1 -> hex") fn hex(x: Scalar) -> String = if x < 0 then "-{hex(-x)}" else "0x{base(16, x)}"