string(3erl)
okay bestie, this is the Erlang module definition for string(3erl) β the <pink>string</pink> module, aka the string-processing main character. Itβs giving iconic, itβs giving useful, and weβre gonna walk through it together. π
β¨ NAME β¨
string β String processing functions. Thatβs the name. Periodt. π
πΈ DESCRIPTION πΈ
okay so this module is literally all about string processing, no cap. A string in this module is represented by unicode:chardata(), which means: a list of codepoints, binaries with UTF-8-encoded codepoints (UTF-8 binaries), or a mix of the two. Slay. π¦
"abcd" is a valid string
<<"abcd">> is a valid string
["abcd"] is a valid string
<<"abc..Γ₯Àâ"/utf8>> is a valid string
<<"abc..Γ₯Àâ">> is NOT a valid string,
but a binary with Latin-1-encoded codepoints
[<<"abc">>, "..Γ₯Àâ"] is a valid string
[atom] is NOT a valid string
This module operates on grapheme clusters. A grapheme cluster is a user-perceived character, which can be represented by several codepoints. The way that works is lowkey fascinating, fr fr.
"Γ₯" [229] or [97, 778]
"e" [101, 778]
The string length of "Γβe" is 3, even though it is represented by the codepoints [223,8593,101,778] or the UTF-8 binary <<195,159,226,134,145,101,204,138>>. See? Main character energy in codepoint form. π
Grapheme clusters for codepoints of class prepend and non-modern (or decomposed) Hangul is not handled for performance reasons in find/3, replace/3, split/2, split/2 and trim/3. Yes, bestie, that list really does say split/2 twice β we keep it exactly as it is. π
Splitting and appending strings is to be done on grapheme clusters borders. There is no verification that the results of appending strings are valid or normalized. So donβt just yolo-append and assume itβs cute. π
Most of the functions expect all input to be normalized to one form, see for example unicode:characters_to_nfc_list/1. Language or locale specific handling of input is not considered in any function. Ngl, thatβs the vibe check: normalize first, bestie. πΈ
π¨ The functions can crash for non-valid input strings. For example, the functions expect UTF-8 binaries but not all functions verify that all binaries are encoded correctly. So yes, invalid input is not it.
Unless otherwise specified the return value type is the same as the input type. That is, binary input returns binary output, list input returns a list output, and mixed input can return a mixed output. Consistent? We love to see it. β¨
1> string:trim(" sarah ").
"sarah"
2> string:trim(<<" sarah ">>).
<<"sarah">>
3> string:lexemes("foo bar", " ").
["foo","bar"]
4> string:lexemes(<<"foo bar">>, " ").
[<<"foo">>,<<"bar">>]
This module has been reworked in Erlang/OTP 20 to handle unicode:chardata() and operate on grapheme clusters. The old functions that only work on Latin-1 lists as input are still available but should not be used, they will be deprecated in a future release. So likeβ¦ donβt get attached to the old ones, okay? π«Ά
π¦ DATA TYPES π¦
direction() = leading | trailinggrapheme_cluster() = char() | [char()]β A user-perceived character, consisting of one or more codepoints.
π EXPORTS π
π casefold/1 π
casefold(String :: unicode:chardata()) -> unicode:chardata()
Converts String to a case-agnostic comparable string. Function casefold/1 is preferred over lowercase/1 when two strings are to be compared for equality. See also equal/4. Bestie, use the right icon for the job. π
Example:
1> string:casefold("Ξ© and αΊ SHARP S").
"Ο and ss sharp s"
π chomp/1 π
chomp(String :: unicode:chardata()) -> unicode:chardata()
Returns a string where any trailing \n or \r\n have been removed from String. Itβs giving βtrim the ending, but make it specific.β π
Example:
182> string:chomp(<<"\nHello\n\n">>).
<<"\nHello">>
183> string:chomp("\nHello\r\r\n").
"\nHello\r"
π equal/2, equal/3, equal/4 π
equal(A, B) -> boolean()
equal(A, B, IgnoreCase) -> boolean()
equal(A, B, IgnoreCase, Norm) -> boolean()
Types:
A = B = unicode:chardata()IgnoreCase = boolean()Norm = none | nfc | nfd | nfkc | nfkd
Returns true if A and B are equal, otherwise false. Simple as that. π
If IgnoreCase is true the function does casefolding on the fly before the equality test.
If Norm is not none the function applies normalization on the fly before the equality test. There are four available normalization forms: nfc, nfd, nfkc, and nfkd.
By default, IgnoreCase is false and Norm is none. The defaults are defaulting, periodt.
Example:
1> string:equal("Γ₯Àâ", <<"Γ₯Àâ"/utf8>>).
true
2> string:equal("Γ₯Àâ", unicode:characters_to_nfd_binary("Γ₯Àâ")).
false
3> string:equal("Γ₯Àâ", unicode:characters_to_nfd_binary("Γ
ΓΓ"), true, nfc).
true
π find/2, find/3 π
find(String, SearchPattern) -> unicode:chardata() | nomatch
find(String, SearchPattern, Dir) -> unicode:chardata() | nomatch
Types:
String = SearchPattern = unicode:chardata()Dir = direction()
Removes anything before SearchPattern in String and returns the remainder of the string or nomatch if SearchPattern is not found. Dir, which can be leading or trailing, indicates from which direction characters are to be searched.
By default, Dir is leading. So it starts from the front unless you tell it otherwise. π¦
Example:
1> string:find("ab..cd..ef", ".").
"..cd..ef"
2> string:find(<<"ab..cd..ef">>, "..", trailing).
<<"..ef">>
3> string:find(<<"ab..cd..ef">>, "x", leading).
nomatch
4> string:find("ab..cd..ef", "x", trailing).
nomatch
πΈ is_empty/1 πΈ
is_empty(String :: unicode:chardata()) -> boolean()
Returns true if String is the empty string, otherwise false. Itβs literally just βis this empty?β and honestly, same. π
Example:
1> string:is_empty("foo").
false
2> string:is_empty(["",<<>>]).
true
π length/1 π
length(String :: unicode:chardata()) -> integer() >= 0
Returns the number of grapheme clusters in String. Not codepoints β grapheme clusters. Keep that distinction close to your heart, bestie. π
Example:
1> string:length("Γβe").
3
2> string:length(<<195,159,226,134,145,101,204,138>>).
3
π§ lexemes/2 π§
lexemes(String :: unicode:chardata(),
SeparatorList :: [grapheme_cluster()]) ->
[unicode:chardata()]
Returns a list of lexemes in String, separated by the grapheme clusters in SeparatorList.
Notice that, as shown in this example, two or more adjacent separator graphemes clusters in String are treated as one. That is, there are no empty strings in the resulting list of lexemes. See also split/3 which returns empty strings.
Notice that [$\r,$\n] is one grapheme cluster. Little detail, big impact. β¨
Example:
1> string:lexemes("abc defxxghix jkl\r\nfoo", "x e" ++ [[$\r,$\n]]).
["abc","def","ghi","jkl","foo"]
2> string:lexemes(<<"abc defxxghix jkl\r\nfoo"/utf8>>, "x e" ++ [$\r,$\n]).
[<<"abc">>,<<"def"/utf8>>,<<"ghi">>,<<"jkl\r\nfoo">>]
π lowercase/1 π
lowercase(String :: unicode:chardata()) -> unicode:chardata()
Converts String to lowercase. Cute, simple, lowercase.
Notice that function casefold/1 should be used when converting a string to be tested for equality. So if equality is the goal, casefold is the icon. π
Example:
2> string:lowercase(string:uppercase("MichaΕ")).
"michaΕ"
β next_codepoint/1 β
next_codepoint(String :: unicode:chardata()) ->
maybe_improper_list(char(), unicode:chardata()) |
{error, unicode:chardata()}
Returns the first codepoint in String and the rest of String in the tail. Returns an empty list if String is empty or an {error, String} tuple if the next byte is invalid. Itβs giving βpeek at the first thing, then hand you the rest.β π«Ά
Example:
1> string:next_codepoint(unicode:characters_to_binary("efg")).
[101|<<"fg"/utf8>>]
π¦ next_grapheme/1 π¦
next_grapheme(String :: unicode:chardata()) ->
maybe_improper_list(grapheme_cluster(),
unicode:chardata()) |
{error, unicode:chardata()}
Returns the first grapheme cluster in String and the rest of String in the tail. Returns an empty list if String is empty or an {error, String} tuple if the next byte is invalid. Same energy as next_codepoint/1, but grapheme-cluster sized. π
Example:
1> string:next_grapheme(unicode:characters_to_binary("efg")).
["e"|<<"fg">>]
π nth_lexeme/3 π
nth_lexeme(String, N, SeparatorList) -> unicode:chardata()
Types:
String = unicode:chardata()N = integer() >= 0SeparatorList = [grapheme_cluster()]
Returns lexeme number N in String, where lexemes are separated by the grapheme clusters in SeparatorList. Itβs the βgive me the Nth oneβ of the lexeme world. π
Example:
1> string:nth_lexeme("abc.def.ghiejkl", 3, ".e").
"ghi"
π pad/2, pad/3, pad/4 π
pad(String, Length) -> unicode:charlist()
pad(String, Length, Dir) -> unicode:charlist()
pad(String, Length, Dir, Char) -> unicode:charlist()
Types:
String = unicode:chardata()Length = integer()Dir = direction() | bothChar = grapheme_cluster()
Pads String to Length with grapheme cluster Char. Dir, which can be leading, trailing, or both, indicates where the padding should be added.
By default, Char is $\s and Dir is trailing. Padding but make it customizable. π
Example:
1> string:pad(<<"HellΓΆ"/utf8>>, 8).
[<<72,101,204,138,108,108,195,182>>,32,32,32]
2> io:format("'~ts'~n",[string:pad("HellΓΆ", 8, leading)]).
3> io:format("'~ts'~n",[string:pad("HellΓΆ", 8, both)]).
π prefix/2 π
prefix(String :: unicode:chardata(), Prefix :: unicode:chardata()) ->
nomatch | unicode:chardata()
If Prefix is the prefix of String, removes it and returns the remainder of String, otherwise returns nomatch. Itβs the βdoes this start with that?β bestie. π«Ά
Example:
1> string:prefix(<<"prefix of string">>, "pre").
<<"fix of string">>
2> string:prefix("pre", "prefix").
nomatch
π₯ replace/3, replace/4 π₯
replace(String, SearchPattern, Replacement) ->
[unicode:chardata()]
replace(String, SearchPattern, Replacement, Where) ->
[unicode:chardata()]
Types:
String = SearchPattern = Replacement = unicode:chardata()Where = direction() | all
Replaces SearchPattern in String with Replacement. Where, default leading, indicates whether the leading, the trailing or all encounters of SearchPattern are to be replaced.
Can be implemented as:
lists:join(Replacement, split(String, SearchPattern, Where)).
Example:
1> string:replace(<<"ab..cd..ef">>, "..", "*").
[<<"ab">>,"*",<<"cd..ef">>]
2> string:replace(<<"ab..cd..ef">>, "..", "*", all).
[<<"ab">>,"*",<<"cd">>,"*",<<"ef">>]
π¦ reverse/1 π¦
reverse(String :: unicode:chardata()) -> [grapheme_cluster()]
Returns the reverse list of the grapheme clusters in String. Itβs giving backwards but make it grapheme-correct. π
Example:
1> Reverse = string:reverse(unicode:characters_to_nfd_binary("Γ
ΓΓ")).
[[79,776],[65,776],[65,778]]
2> io:format("~ts~n",[Reverse]).
OAAΜ
πΈ slice/2, slice/3 πΈ
slice(String, Start) -> Slice
slice(String, Start, Length) -> Slice
Types:
For slice, the type block is this cutie:
String = unicode:chardata()
Start = integer() >= 0
Length = infinity | integer() >= 0
Slice = unicode:chardata()
Bestie, this returns a substring of String with at most Length grapheme clusters, starting at position Start β¨. By default, Length is infinity, periodt.
Example:
1> string:slice(<<"HellΓΆ WΓΆrld"/utf8>>, 4).
<<"ΓΆ WΓΆrld"/utf8>>
2> string:slice(["HellΓΆ ", <<"WΓΆrld"/utf8>>], 4,4).
"ΓΆ WΓΆ"
3> string:slice(["HellΓΆ ", <<"WΓΆrld"/utf8>>], 4,50).
"ΓΆ WΓΆrld"
πΈ split/2, split/3 πΈ
split(String, SearchPattern) -> [unicode:chardata()]
split(String, SearchPattern, Where) -> [unicode:chardata()]
Types:
String = SearchPattern = unicode:chardata()
Where = direction() | all
This splits String wherever SearchPattern is encountered and returns the remaining parts π
. Where defaults to leading and tells it whether the leading, trailing, or all encounters of SearchPattern will split String.
Example:
0> string:split("ab..bc..cd", "..").
["ab","bc..cd"]
1> string:split(<<"ab..bc..cd">>, "..", trailing).
[<<"ab..bc">>,<<"cd">>]
2> string:split(<<"ab..bc....cd">>, "..", all).
[<<"ab">>,<<"bc">>,<<>>,<<"cd">>]
π¦ take/2, take/3, take/4 π¦
take(String, Characters) -> {Leading, Trailing}
take(String, Characters, Complement) -> {Leading, Trailing}
take(String, Characters, Complement, Dir) -> {Leading, Trailing}
Types:
String = unicode:chardata()
Characters = [grapheme_cluster()]
Complement = boolean()
Dir = direction()
Leading = Trailing = unicode:chardata()
This takes characters from String as long as those characters are members of the set Characters or the complement of the set Characters π«Ά. Dir can be leading or trailing and says from which direction characters are taken.
Example:
5> string:take("abc0z123", lists:seq($a,$z)).
{"abc","0z123"}
6> string:take(<<"abc0z123">>, lists:seq($0,$9), true, leading).
{<<"abc">>,<<"0z123">>}
7> string:take("abc0z123", lists:seq($0,$9), false, trailing).
{"abc0z","123"}
8> string:take(<<"abc0z123">>, lists:seq($a,$z), true, trailing).
{<<"abc0z">>,<<"123">>}
π titlecase/1 π
titlecase(String :: unicode:chardata()) -> unicode:chardata()
Converts String to titlecase π.
Example:
1> string:titlecase("Γ is a SHARP s").
"Ss is a SHARP s"
π to_float/1 π
to_float(String) -> {Float, Rest} | {error, Reason}
Types:
String = unicode:chardata()
Float = float()
Rest = unicode:chardata()
Reason = no_float | badarg
String is expected to start with a valid float represented as text β the digits are ASCII values π. Any remaining characters in the string after the float are returned in Rest.
Example:
> {F1,Fs} = string:to_float("1.0-1.0e-1"),
> {F2,[]} = string:to_float(Fs),
> F1+F2.
0.9
> string:to_float("3/2=1.5").
{error,no_float}
> string:to_float("-1.5eX").
{-1.5,"eX"}
π to_integer/1 π
to_integer(String) -> {Int, Rest} | {error, Reason}
Types:
String = unicode:chardata()
Int = integer()
Rest = unicode:chardata()
Reason = no_integer | badarg
String is expected to start with a valid integer represented as text β the digits are ASCII values π. Any remaining characters in the string after the integer are returned in Rest.
Example:
> {I1,Is} = string:to_integer("33+22"),
> {I2,[]} = string:to_integer(Is),
> I1-I2.
11
> string:to_integer("0.5").
{0,".5"}
> string:to_integer("x=2").
{error,no_integer}
π§ to_graphemes/1 π§
to_graphemes(String :: unicode:chardata()) -> [grapheme_cluster()]
Converts String to a list of grapheme clusters πΈ.
Example:
1> string:to_graphemes("Γβe").
[223,8593,[101,778]]
2> string:to_graphemes(<<"Γβe"/utf8>>).
[223,8593,[101,778]]
π trim/1, trim/2, trim/3 π
trim(String) -> unicode:chardata()
trim(String, Dir) -> unicode:chardata()
trim(String, Dir, Characters) -> unicode:chardata()
Types:
String = unicode:chardata()
Dir = direction() | both
Characters = [grapheme_cluster()]
This returns a string where leading, trailing, or both sets of Characters have been removed π. Dir can be leading, trailing, or both and says from which direction characters are removed.
The default Characters is the set of nonbreakable whitespace codepoints, defined as Pattern_White_Space in Unicode Standard Annex #31. By default, Dir is both. Notice that [$\r,$\n] is one grapheme cluster according to the Unicode Standard π.
Example:
1> string:trim("\t Hello \n").
"Hello"
2> string:trim(<<"\t Hello \n">>, leading).
<<"Hello \n">>
3> string:trim(<<".Hello.\n">>, trailing, "\n.").
<<".Hello">>
β uppercase/1 β
uppercase(String :: unicode:chardata()) -> unicode:chardata()
Converts String to uppercase π₯. See also titlecase/1.
Example:
1> string:uppercase("MichaΕ").
"MICHAΕ"
π°οΈ OBSOLETE API FUNCTIONS π°οΈ
Here follows the function of the old API. These functions only work on a list of Latin-1 characters.
π Note
The functions are kept for backward compatibility, but are not recommended. They will be deprecated in a future release.
Any undocumented functions in string are not to be used.
centre(String, Number) -> Centered Β· centre(String, Number, Character) -> Centered
Types: String = Centered = string(), Number = integer() >= 0, Character = char()
Okay so this one takes your string and centres it β surrounded by blanks, or by Character if you passed one β and the resulting string has length exactly Number. Very demure, very symmetric π
This function is obsolete. Use pad/3.
chars(Character, Number) -> String Β· chars(Character, Number, Tail) -> String
Types: Character = char(), Number = integer() >= 0, Tail = String = string()
Returns a string made of Number copies of Character β and optionally the whole thing ends with the string Tail π
This function is obsolete. Use lists:duplicate/2.
chr(String, Character) -> Index
Types: String = string(), Character = char(), Index = integer() >= 0
Gives you the index of the first time Character shows up in String. If it literally never shows up? You get 0. no cap π
This function is obsolete. Use find/2.
concat(String1, String2) -> String3
Types: String1 = String2 = String3 = string()
Glues String1 and String2 together into a brand-new String3 and hands it right back to you. Iconic behaviour tbh π₯
This function is obsolete. Use[String1, String2]as theDataargument, and callunicode:characters_to_list/2orunicode:characters_to_binary/2to flatten the output.
copies(String, Number) -> Copies
Types: String = Copies = string(), Number = integer() >= 0
Returns a string with String repeated Number times. That's it, that's the function β¨
This function is obsolete. Use lists:duplicate/2.
cspan(String, Chars) -> Length
Types: String = Chars = string(), Length = integer() >= 0
Gives you the length of the longest initial chunk of String that is made entirely of characters that are NOT in Chars. Basically the opposite vibe of span π¦
This function is obsolete. Use take/3.
Example:
> string:cspan("\t abcdef", " \t").
0
join(StringList, Separator) -> String
Types: StringList = [string()], Separator = String = string()
Takes all the strings in StringList and returns one string with them separated by Separator. the way that this just works ππ
This function is obsolete. Use lists:join/2.
Example:
> join(["one", "two", "three"], ", ").
"one, two, three"
left(String, Number) -> Left Β· left(String, Number, Character) -> Left
Types: String = Left = string(), Number = integer() >= 0, Character = char()
Returns String with its length adjusted to Number, with the left margin fixed. So if length(String) < Number, the string gets padded with blanks or with Characters on the right side πΈ
This function is obsolete. Usepad/2orpad/3.
Example:
> string:left("Hello",10,$.).
"Hello....."
len(String) -> Length
Types: String = string(), Length = integer() >= 0
Returns the number of characters in String. humble but essential π
This function is obsolete. Use length/1.
rchr(String, Character) -> Index
Types: String = string(), Character = char(), Index = integer() >= 0
The last occurrence of Character in String, please and thank you. No occurrence? 0. π
This function is obsolete. Use find/3.
right(String, Number) -> Right Β· right(String, Number, Character) -> Right
Types: String = Right = string(), Number = integer() >= 0, Character = char()
Returns String with its length adjusted to Number, but this time the right margin is fixed. If the length of (String) < Number, String gets padded with blanks or Characters β on the left, so it hugs the right edge π«Ά
This function is obsolete. Use pad/3.
Example:
> string:right("Hello", 10, $.).
".....Hello"
rstr(String, SubString) -> Index
Types: String = SubString = string(), Index = integer() >= 0
Returns the position where the last occurrence of SubString begins in String. If SubString doesn't exist in String at all, you get 0 π
This function is obsolete. Use find/3.
Example:
> string:rstr(" Hello Hello World World ", "Hello World").
8
span(String, Chars) -> Length
Types: String = Chars = string(), Length = integer() >= 0
Returns the length of the longest initial chunk of String that consists entirely of characters from Chars. the matching set to cspan π
This function is obsolete. Use take/2.
Example:
> string:span("\t abcdef", " \t").
5
str(String, SubString) -> Index
Types: String = SubString = string(), Index = integer() >= 0
Returns the position where the first occurrence of SubString begins in String. And if SubString isn't in String? 0. we love a consistent return value β¨
This function is obsolete. Use find/2.
Example:
> string:str(" Hello Hello World World ", "Hello World").
8
strip(String :: string()) -> string() Β· strip(String, Direction) -> Stripped Β· strip(String, Direction, Character) -> Stripped
Types: String = Stripped = string(), Direction = left | right | both, Character = char()
Returns a string with the leading, trailing, or both kinds of blanks removed β or a bunch of Characters removed instead. Direction (which is left, right, or both) tells you which side the blanks get yeeted from. And strip/1 is exactly the same as strip(String, both) π§
This function is obsolete. Use trim/3.
Example:
> string:strip("...Hello.....", both, $.).
"Hello"
sub_string(String, Start) -> SubString Β· sub_string(String, Start, Stop) -> SubString
Types: String = SubString = string(), Start = Stop = integer() >= 1
Returns a substring of String, starting at position Start and running to the end of the string β or to and including position Stop if you gave one. inclusive, generous, lovely π
This function is obsolete. Use slice/3.
Example:
sub_string("Hello World", 4, 8).
"lo Wo"
substr(String, Start) -> SubString Β· substr(String, Start, Length) -> SubString
Types: String = SubString = string(), Start = integer() >= 1, Length = integer() >= 0
Returns a substring of String starting at position Start, ending either at the end of the string or after Length characters π
This function is obsolete. Use slice/3.
Example:
> substr("Hello World", 4, 5).
"lo Wo"
sub_word(String, Number) -> Word Β· sub_word(String, Number, Character) -> Word
Types: String = Word = string(), Number = integer(), Character = char()
Returns the word sitting in position Number of String, where words are separated by blanks or by Character. lowkey useful, highkey obsolete π
This function is obsolete. Use nth_lexeme/3.
Example:
> string:sub_word(" Hello old boy !",3,$o).
"ld b"
to_lower(String) -> Result Β· to_lower(Char) -> CharResult Β· to_upper(String) -> Result Β· to_upper(Char) -> CharResult
Types: String = Result = io_lib:latin1_string(), Char = CharResult = char()
Case-converts the string or character you hand it. Big heads-up though: the supported character set is ISO/IEC 8859-1 (also called Latin 1), and all values outside this set are unchanged β so don't come crying to me when your fancy glyphs stay exactly as they were π
This function is obsolete, uselowercase/1,uppercase/1,titlecase/1orcasefold/1.
tokens(String, SeparatorList) -> Tokens
Types: String = SeparatorList = string(), Tokens = [Token :: nonempty_string()]
Returns a list of the tokens in String, split on the characters in SeparatorList π
Example:
> tokens("abc defxxghix jkl", "x ").
["abc", "def", "ghi", "jkl"]
Notice that, as shown in this example, two or more adjacent separator characters in String are treated as one. That is, there are no empty strings in the resulting list of tokens. no ghost tokens, we don't do that here π»
This function is obsolete. Use lexemes/2.
words(String) -> Count Β· words(String, Character) -> Count
Types: String = string(), Character = char(), Count = integer() >= 1
Returns the number of words in String, separated by blanks or by Character. counting era π’
This function is obsolete. Use lexemes/2.
Example:
> words(" Hello old boy!", $o).
4
π NOTES π
Okay bestie, real talk: some of the general string functions can seem to overlap each other. The reason is that this string package is the combination of two earlier packages and all functions of both packages have been retained. so it's not redundant, it's β¨heritageβ¨ iykyk
and that's the whole page, bestie!! ππ
This absolute icon of a manual page comes from Ericsson AB, ships with stdlib 4.3.1.3, and lives at string(3erl). Go forth and pad, trim, and slice responsibly π«Άβ¨π
π¦ where this page comes from
- ubuntu noble: erlang-manpages 1:25.3.2.8+dfsg-1ubuntu4 Β· Apache-2.0
- ubuntu noble: erlang-manpages 1:25.3.2.8+dfsg-1ubuntu4.8 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms