add_backslash (3m_unicode) - [M_unicode:CONVERSION] Convert UTF-8 encoded data to ASCII-7 C-style backslash escape sequences (LICENSE:MIT) add_border (3m_unicode) - [M_unicode:EDITING] add border of UTF8-encoded box characters (LICENSE:MIT) adjustl (3m_unicode) - [M_unicode:WHITESPACE] Left-justified a string (LICENSE:MIT) adjustr (3m_unicode) - [M_unicode:WHITESPACE] right-justify a string (LICENSE:MIT) character (3m_unicode) - [M_unicode:CONVERSION] convert type(unicode_type) string to a CHARACTER variable (LICENSE:MIT) codepoints_to_utf8 (3m_unicode) - [M_unicode:CONVERSION] convert codepoints to CHARACTER (LICENSE:MIT) escape (3m_unicode) - [M_unicode:CONVERSION] expand C++ escape sequences (LICENSE:MIT) expand_html (3m_unicode) - [M_unicode:ENCODE] expand HTML character entities ("&NAME;" strings) (LICENSE:MIT) expandtabs (3m_unicode) - [M_unicode:WHITESPACE] function to expand tab characters (LICENSE:MIT) fmt (3m_unicode) - [M_unicode:CONVERSION] convert any intrinsic to a string using specified format (LICENSE:MIT) get_arg (3m_unicode) - [M_unicode:SYSTEM] get command line argument (LICENSE:MIT) get_env (3m_unicode) - [M_unicode:SYSTEM] return value of environment variable (LICENSE:MIT) glob (3m_unicode) - [M_unicode:COMPARE] compare given string for match to a pattern which may contain globbing wildcard characters (LICENSE:MIT) ichar (3m_unicode) - [M_unicode:CONVERSION] character-to-integer code conversion function (LICENSE:MIT) index (3m_unicode) - [M_unicode:SEARCH] Position of a substring within a string (LICENSE:MIT) isascii (3m_unicode) - [M_unicode:QUERY] returns .true. if all the characters of a string are in the set from CHAR(0) to CHAR(127). (LICENSE:MIT) isblank (3m_unicode) - [M_unicode:COMPARE] returns .true. if character is a Unicode or ASCII-7 blank character (space or horizontal tab) . (LICENSE:MIT) isspace (3m_unicode) - [M_unicode:COMPARE] returns .true. if character is a null, space, tab, carriage return, new line, vertical tab, or formfeed (LICENSE:MIT) join (3m_unicode) - [M_unicode:EDITING] append CHARACTER variable array into a single CHARACTER variable with specified separator (LICENSE:MIT) len (3m_unicode) - [M_unicode:WHITESPACE] Length of a string (LICENSE:MIT) len_trim (3m_unicode) - [M_unicode:WHITESPACE] string length without trailing blank characters (LICENSE:MIT) lower (3m_unicode) - [M_unicode:CASE] changes a string to lowercase over specified range (LICENSE:MIT) M_unicode (3m_unicode) - [M_unicode::INTRO] Unicode string module (LICENSE:MIT) pad (3m_unicode) - [M_unicode:PAD] return string padded to at least specified length (LICENSE:MIT) pound_to_box (3m_unicode) - [M_unicode:EDITING] convert pound character to box characters (LICENSE:MIT) readline (3m_unicode) - [M_unicode:IO] read a line from specified LUN into string up to line length limit (LICENSE:MIT) repeat (3m_unicode) - [M_unicode:PAD] Repeated string concatenation (LICENSE:MIT) replace (3m_unicode) - [M_unicode:EDITING] function replaces one substring for another in string (LICENSE:MIT) reverse (3m_unicode) - [M_unicode:CASE] reverse order of glyphs on a line (LICENSE:MIT) scan (3m_unicode) - [M_unicode:SEARCH] Scan a string for the presence of a set of characters (LICENSE:MIT) slurp (3m_unicode) - [M_unicode:READ] read formatted UTF-8 file into a TYPE(UNICODE_TYPE) string array (LICENSE:MIT) sort (3m_unicode) - [M_unicode:SORT] indexed hybrid quicksort of an array (LICENSE:MIT) split (3m_unicode) - [M_unicode:PARSE] parse a string into tokens, one at a time. (LICENSE:MIT) sub (3m_unicode) - [M_unicode:EDITING] Return substring (LICENSE:MIT) tokenize (3m_unicode) - [M_unicode:PARSE] Parse a string into tokens. (LICENSE:MIT) transliterate (3m_unicode) - [M_unicode:EDITING] replace characters from old set with new set (LICENSE:MIT) trim (3m_unicode) - [M_unicode:WHITESPACE] remove trailing blank characters from a string (LICENSE:MIT) upper (3m_unicode) - [M_unicode:CASE] changes a string to uppercase (LICENSE:MIT) utf8_to_codepoints (3m_unicode) - [M_unicode:CONVERSION] Convert UTF-8-encoded data to Unicode codepoints (LICENSE:MIT) verify (3m_unicode) - [M_unicode:SEARCH] Position of a character in a string of characters that does not appear in a given set of characters. (LICENSE:MIT) add_border(3m_unicode) add_border(3m_unicode) NAME add_border(3f) - [M_unicode:EDITING] add border of UTF8-encoded box characters (LICENSE:MIT) SYNOPSIS syntax: function add_border(win,style) result(winout) type(unicode_type)|character(len=*),intent(in) :: win(:)|win type(unicode_type)|character(len=*),intent(in),optional :: style type(unicode_type),allocatable :: winout(:) CHARACTERISTICS • WIN can be a scaler or vector of CHARACTER or TYPE(UNICODE_TYPE) strings. • WINOUT elements will all have the length of the longest element of WIN DESCRIPTION Add a border of box characters around character or string (ie. type(unicode_type)) scalar or vector text. OPTIONS win input array to be changed style may be "light", "bold", or "double". Default is "bold". RETURNS winout an array of strings with a box character border added EXAMPLES Sample Program: program demo_add_border use M_unicode, only : ut=>unicode_type, assignment(=) use M_unicode, only : character, add_border, trim implicit none type(ut),allocatable :: textout(:) type(ut) :: uline type(ut),allocatable :: uparagraph(:) character(len=*),parameter :: paragraph(*)=[character(len=10) :: & &'one',& &'two',& &'three',& &'four'] ! show original text textout=paragraph call write_text() ! character array textout=add_border(paragraph) call write_text() ! ragged string array uparagraph=paragraph uparagraph=trim(uparagraph) textout=add_border(uparagraph) call write_text() ! add another border and specify style textout=add_border(textout,style='DOUBLE') call write_text() ! scalar character textout=add_border("To be or not to be!",style='DOUBLE') call write_text() ! scalar string uline="To be or not to be!" textout=add_border(uline,style='light') call write_text() contains subroutine write_text() integer :: i write(*,'(*(a:))',advance='no') & & (trim(textout(i)%character()), & & new_line('a'), & & i=1,size(textout)) end subroutine write_text end program demo_add_border Results: > > one > two > three > four > ┏━━━━━━━━━━┓ > ┃one ┃ > ┃two ┃ > ┃three ┃ > ┃four ┃ > ┗━━━━━━━━━━┛ > ┏━━━━━┓ > ┃one ┃ > ┃two ┃ > ┃three┃ > ┃four ┃ > ┗━━━━━┛ > ╔═══════╗ > ║┏━━━━━┓║ > ║┃one ┃║ > ║┃two ┃║ > ║┃three┃║ > ║┃four ┃║ > ║┗━━━━━┛║ > ╚═══════╝ > ╔═══════════════════╗ > ║To be or not to be!║ > ╚═══════════════════╝ > ┌───────────────────┐ > │To be or not to be!│ > └───────────────────┘ AUTHOR John S. Urban LICENSE MIT May 05, 2026 add_border(3m_unicode) adjustr(3m_unicode) adjustr(3m_unicode) NAME ADJUSTR(3f) - [M_unicode:WHITESPACE] right-justify a string (LICENSE:MIT) SYNOPSIS result = adjustr(string,glyphs) elemental function adjustr(string) type(unicode_type) :: adjustr type(unicode_type),intent(in) :: string integer,intent(in),optional :: glyphs CHARACTERISTICS • STRING is a string variable • GLYPHS is a default integer • the return value is a string variable DESCRIPTION ADJUSTR(3) right-justifies a string by removing trailing spaces. Spaces are inserted at the start of the string as needed to retain the original length unless an explicit return length is specified by the GLYPHS parameter. OPTIONS • STRING : the string to right-justify • GLYPHS : length in glyphs to extend to or truncate to RESULT trailing spaces are removed and the same number of spaces are then inserted at the start of string. EXAMPLES sample program: program demo_adjustr use M_unicode, only : ut=>unicode_type use M_unicode, only : adjustr, len use M_unicode, only : write(formatted) use M_unicode, only : assignment(=) implicit none type(ut) :: str type(ut),allocatable :: array(:) integer :: i character(len=*),parameter :: bracket='("[",DT,"]")' ! call numberline(2) ! ! basic usage str = ' sample string ' write(*,bracket) str str = adjustr(str) write(*,bracket) str ! call numberline(5) ! ! elemental array=ut([character(len=50) :: & ' एक (ek) ', & ' दो (do) ', & ' तीन(teen) ' ]) ! ! print array unadjusted write(*,bracket)array !do i=1,size(array) ! write(*,'(*(g0,1x))')array(i)%codepoint() !enddo ! note 50 bytes is not necessarily 50 glyphs write(*,'(*(g0,1x))')'length in glyphs=',len(array) write(*,'(*(g0,1x))')'length in bytes=',(len(array(i)%character()),i=1,size(array)) ! call numberline(5) ! ! print array right-justified write(*,bracket)adjustr(array) ! call numberline(5) ! ! print array right-justified specifying number of glyphs write(*,*)'set to 50' write(*,bracket)adjustr(array,50) ! write(*,*)'set to 60' call numberline(6) write(*,bracket)adjustr(array,60) write(*,*)'set to 40' call numberline(4) write(*,bracket)adjustr(array,40) write(*,*)'set to 10' call numberline(1) write(*,bracket)adjustr(array,10) write(*,*)'set to 5' write(*,bracket)adjustr(array,5) write(*,*)'set to 4' write(*,bracket)adjustr(array,4) write(*,*)'set to 1' write(*,bracket)adjustr(array,1) contains ! subroutine numberline(ireps) integer,intent(in) :: ireps write(*,'(1x,a)')repeat('1234567890',ireps) end subroutine numberline end program demo_adjustr Results: > 12345678901234567890 > [ sample string ] > [ sample string] > 12345678901234567890123456789012345678901234567890 > [ एक (ek) ] > [ दो (do) ] > [ तीन(teen) ] > length in glyphs= 46 46 44 > length in bytes= 50 50 50 > 12345678901234567890123456789012345678901234567890 > [ एक (ek)] > [ दो (do)] > [ तीन(teen)] > 12345678901234567890123456789012345678901234567890 > set to 50 > [ एक (ek)] > [ दो (do)] > [ तीन(teen)] > set to 60 > 123456789012345678901234567890123456789012345678901234567890 > [ एक (ek)] > [ दो (do)] > [ तीन(teen)] > set to 40 > 1234567890123456789012345678901234567890 > [ एक (ek)] > [ दो (do)] > [ तीन(teen)] > set to 10 > 1234567890 > [ एक (ek)] > [ दो (do)] > [ तीन(teen)] > set to 5 > [ (ek)] > [ (do)] > [teen)] > set to 4 > [(ek)] > [(do)] > [een)] > set to 1 > [)] > [)] > [)] SEE ALSO ADJUSTL(3), TRIM(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 adjustr(3m_unicode) character(3m_unicode) character(3m_unicode) NAME CHARACTER(3f) - [M_unicode:CONVERSION] convert type(unicode_type) string to a CHARACTER variable (LICENSE:MIT) SYNOPSIS result = character(STRING,start,end,inc) or result = STRING%character(start,end,inc) elemental function character(string,start,end,inc) type(unicode_type),intent(in) :: string integer,intent(in) :: start integer,intent(in) :: end integer,intent(in) :: inc CHARACTERISTICS • STRING is a scalar or array string variable • the returned value is a CHARACTER scalar or array DESCRIPTION CHARACTER(3f) returns a CHARACTER variable given a string variable of type type(unicode_type). OPTIONS • STRING : A scalar or array string to convert to intrinsic CHARACTER type. RESULT The result converts each string to bytes stored in CHARACTER variables. All elements will be padded to the same length of the longest element; as all elements of a CHARACTER array are required to be of the same length. Commonly used to pass data to procedures requiring CHARACTER variables or for printing when the DT format is not used.. EXAMPLES Sample program program demo_character use M_unicode, only : ut=>unicode_type, ch=>character, trim, len, pad use M_unicode, only : write(formatted), assignment(=) type(ut) :: ustr type(ut),allocatable :: array(:) integer :: i character(len=*),parameter :: all='(*(g0))' ustr=[949, 8021, 961, 951, 954, 945, 33] ! eureka in codepoints ! when doing I/O using DT might be the most intuitive ! but sometimes converting to intrinsic character variables ! is preferred write (*,all) ch(ustr) ! convert to CHARACTER variable write (*,all) ustr%character() ! convert to CHARACTER variable ! you can select a range of glyphs write (*,all) ustr%character(3,4) ! similar to LINE(3:4) for ! CHARACTER variables ! and even reverse a string write (*,all) ustr%character(len(ustr),1,-1) ! reverse string ! note that OOP syntax provides a few other options write (*,all) ustr%byte() ! convert to CHARACTER(LEN=1) type ! arrays ! ! using this syntax make sure to make the LEN value large enough ! that glyphs can take up to four bytes array= ut([ character(len=60) :: & 'Confucius never claimed to be a prophet, ' ,& 'but I think he foresaw AI! He said ' ,& '' ,& ' "学而不思则罔,思而不学则殆"' ,& 'or' ,& ' (xué ér bù sī zé wǎng, sī ér bù xué zé dài),' ,& 'which is also' ,& ' "To learn without thinking is to be lost, ' ,& ' to think without learning is to be in danger".']) ! write(*,'(*(:,"[",g0,"]",/))')ch(array) ! all elements will be the same length in bytes but not necessarily !in glyphs write(*,'(a,*(i0,1x))')'all elements the same length in BYTES:', & & len(ch(array)) write(*,'(a,*(i0,1x))')'lengths (in glyphs):',len(array) array=trim(array) write(*,'(a,*(i0,1x))')'lengths after trimming (in glyphs):', & & len(array) write(*,'(:*(:,"[",g0,"]",/))')ch(array) write(*,*) ! ! using this syntax the elements will be of different lengths array= [ & ut('Confucius never claimed to be a prophet,') ,& ut('but I think he foresaw AI! He said') ,& ut('') ,& ut(' "学而不思则罔,思而不学则殆"') ,& ut('or') ,& ut(' (xué ér bù sī zé wǎng, sī ér bù xué zé dài),') ,& ut('which is also') ,& ut(' "To learn without thinking is to be lost,') ,& ut(' to think without learning is to be in danger".')] ! but using the CHARACTER function will still make them the same ! length in bytes so you might want to print them individually ! for certain effects, subject to font properties such as varying ! glyph widths. write(*,'(*("[",g0,"]",/))')(ch(array(i)),i=1,size(array)) write(*,'(*("[",g0,"]",/))')(ch(pad(array(i),60)),i=1,size(array)) ! end program demo_character Results: > εὕρηκα! > εὕρηκα! > ρη > !ακηρὕε > εὕρηκα! > [Confucius never claimed to be a prophet, ] > [but I think he foresaw AI! He said ] > [ ] > [ "学而不思则罔,思而不学则殆" ] > [or ] > [ (xué ér bù sī zé wǎng, sī ér bù xué zé dài), ] > [which is also ] > [ "To learn without thinking is to be lost, ] > [ to think without learning is to be in danger". ] > > all elements the same length in BYTES:60 > lengths (in glyphs):60 60 60 34 60 48 60 60 60 > lengths after trimming (in glyphs):40 34 0 16 2 45 13 42 47 > [Confucius never claimed to be a prophet, ] > [but I think he foresaw AI! He said ] > [ ] > [ "学而不思则罔,思而不学则殆" ] > [or ] > [ (xué ér bù sī zé wǎng, sī ér bù xué zé dài),] > [which is also ] > [ "To learn without thinking is to be lost, ] > [ to think without learning is to be in danger". ] > > > [Confucius never claimed to be a prophet,] > [but I think he foresaw AI! He said] > [] > [ "学而不思则罔,思而不学则殆"] > [or] > [ (xué ér bù sī zé wǎng, sī ér bù xué zé dài),] > [which is also] > [ "To learn without thinking is to be lost,] > [ to think without learning is to be in danger".] > [ > [Confucius never claimed to be a prophet, ] > [but I think he foresaw AI! He said ] > [ ] > ["学而不思则罔,思而不学则殆" ] > [or ] > [(xué ér bù sī zé wǎng, sī ér bù xué zé dài), ] > [which is also ] > ["To learn without thinking is to be lost, ] > [to think without learning is to be in danger". ] > [ SEE ALSO functions that perform operations on character strings: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • non-elemental: len_trim(3), len(3), repeat(3), trim(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 character(3m_unicode) expandtabs(3m_unicode) expandtabs(3m_unicode) NAME EXPANDTABS(3f) - [M_unicode:WHITESPACE] function to expand tab characters (LICENSE:MIT) SYNOPSIS elemental function expandtabs(INSTR,TABSIZE) result(OUT) type(unicode_type),intent=(in) :: INSTR integer,intent(in),optional :: TAB_SIZE type(unicode_type) :: OUT DESCRIPTION EXPANDTABS(3) expands tabs in INSTR to spaces in OUT. It assumes a tab is set every 8 characters by default. Trailing spaces are removed. OPTIONS instr Input line to remove tabs from tab_size spacing between tab stops. RETURNS out Output string with tabs expanded. EXAMPLES Sample program: program demo_expandtabs use M_unicode, only : expandtabs, ch=>character, replace use M_unicode, only : assignment(=), ut=> unicode_type implicit none type(ut) :: in type(ut) :: inexpanded character(len=:),allocatable :: dat integer :: i dat=' this is my string ' ! change spaces to tabs to make a sample input do i=1,len(dat) if(dat(i:i) == ' ')dat(i:i)=char(9) enddo in=dat ! inexpanded=expandtabs(in) write(*,'("[",a,"]")')ch(inexpanded) inexpanded=replace(inexpanded,ut(' '),ut('_')) write(*,'("[",a,"]")')ch(inexpanded) ! write(*,'("[",a,"]")')ch(in%expandtabs()) write(*,'("[",a,"]")')ch(in%expandtabs(tab_size=8)) write(*,'("[",a,"]")')ch(in%expandtabs(tab_size=1)) write(*,'("[",a,"]")')ch(in%expandtabs(tab_size=0)) ! end program demo_expandtabs Results: > [ this is my string] > [________________this____is______my______string] > [ this is my string] > [ this is my string] > [ this is my string] > [thisismystring] AUTHOR John S. Urban LICENSE MIT May 05, 2026 expandtabs(3m_unicode) fmt(3m_unicode) fmt(3m_unicode) NAME FMT(3f) - [M_unicode:CONVERSION] convert any intrinsic to a string using specified format (LICENSE:MIT) SYNOPSIS function fmt(value,format) result(string) class(*),intent(in),optional :: value character(len=*),intent(in),optional :: format or type(unicode_type),intent(in),optional :: format type(unicode_type) :: string DESCRIPTION FMT(3f) converts any standard intrinsic value to a string using the specified format. OPTIONS value value to print the value of. May be of type INTEGER, LOGICAL, REAL, DOUBLEPRECISION, COMPLEX, or CHARACTER as well as TYPE(UNICODE_TYPE). format format to use to print value. It is up to the user to use an appropriate format. The format does not require being surrounded by parenthesis. If not present a default is selected similar to what would be produced with free format, with trailing zeros removed. RETURNS string A string value EXAMPLES Sample program: program demo_fmt use :: M_unicode, only : fmt, assignment(=) use :: M_unicode, only : ut=>unicode_type, ch=>character implicit none character(len=:),allocatable :: Astr, Aformat type(ut) :: Ustr ! format can be CHARACTER Aformat="('[',i0,']')" Astr=fmt(10,Aformat) write(*,*)'result is ',Astr ! format can be string Astr=fmt(10.0/3.0,ut("'[',g0.5,']'")) write(*,*)'result is ',Astr ! Output is a string, so use ch() write(*,*)'result is ', ch(fmt(.true.,"'The answer is [',g0,']'")) ! OOP Ustr='A B C' Ustr=Ustr%fmt("'[',g0,']'") write(*,*)'result is ',ch(Ustr) end program demo_fmt Results: > result is [10] > result is [3.3333] > result is The final answer is [T] > result is [A B C] AUTHOR John S. Urban LICENSE MIT May 05, 2026 fmt(3m_unicode) get_arg(3m_unicode) get_arg(3m_unicode) NAME get_arg(3f) - [M_unicode:SYSTEM] get command line argument (LICENSE:MIT) SYNOPSIS impure elemental function get_arg(position,default) result(out) integer,intent=(in) :: position type(unicode_type),optional :: default type(unicode_type) :: out CHARACTERISTICS DEFAULT may be default CHARACTER type as well. DESCRIPTION get_arg(3) gets the value of the requested command line argument as TYPE(UNICODE_TYPE) . OPTIONS position Position on command line of requested argument. Zero returns the name of the command executed. Non-existent positions default to returning a null string. default value to return if argument is not set or set to a blank value RETURNS out value assigned based on value of environment variable NAME EXAMPLES Sample program: program demo_get_arg use M_unicode, only : get_arg, ut=> unicode_type, ch=>character use M_unicode, only : assignment(=), operator(//), write(formatted) implicit none integer :: position type(ut) :: default type(ut) :: value type(ut) :: smiley integer :: i character(len=*),parameter :: bracket= '(1x,*("[",a,"]",:))' ! smiley=128515 ! set with Unicode code point default='Wish I was first '//smiley//'!' ! set with unicode_type ! ! arguments can be type(unicode_type) or character ! but type(unicode_type) is always returned do position=0,command_argument_count() value=get_arg(position, default ) value=get_arg(position, default%character() ) ! write(*,*)value%character() write(*,'(DT)')default%get_arg(position) ! ! print each glyph surrounded by brackets write(*,bracket)(value%character(i,i),i=1,value%len()) enddo ! end program demo_get_arg Results: > demo_get_arg > demo_get_arg > [d][e][m][o][_][g][e][t][_][a][r][g] AUTHOR John S. Urban LICENSE MIT May 05, 2026 get_arg(3m_unicode) get_env(3m_unicode) get_env(3m_unicode) NAME get_env(3f) - [M_unicode:SYSTEM] return value of environment variable (LICENSE:MIT) SYNOPSIS impure elemental function get_env(name,default) result(out) type(unicode_type),intent=(in) :: name type(unicode_type),optional :: default type(unicode_type) :: out CHARACTERISTICS NAME and DEFAULT may be default CHARACTER type as well. DESCRIPTION get_env(3) gets the value of the requested environment variable as TYPE(UNICODE_TYPE) . OPTIONS name name of environment variable to return the value of. Typically the name may only contain the characters A-Z,a-z,0-9 and underscore; but allowed values are system-dependent. default value to return if environment variable NAME is not set or set to a blank value RETURNS out value assigned based on value of environment variable NAME EXAMPLES Sample program: program demo_get_env use M_unicode, only : get_env, ut=> unicode_type use M_unicode, only : assignment(=), operator(//) implicit none type(ut) :: name type(ut) :: default type(ut) :: value type(ut) :: smiley integer :: i character(len=*),parameter :: bracket= '(1x,*("[",a,"]",:))' ! smiley=128515 ! set with Unicode code point name='UTF8' ! set with ASCII default='Have a nice day '//smiley//'!' ! set with unicode_type ! ! arguments can be type(unicode_type) or character ! but type(unicode_type) is always returned value=get_env(name, default ) value=get_env(name%character(), default%character() ) value=get_env(name, default%character() ) value=get_env(name%character(), default ) ! write(*,*)value%character() ! ! print each glyph surrounded by brackets write(*,bracket)(value%character(i,i),i=1,value%len()) ! end program demo_get_env Results: > Have a nice day 😃! > [H][a][v][e][ ][a][ ][n][i][c][e][ ][d][a][y][ ][😃][!] AUTHOR John S. Urban LICENSE MIT May 05, 2026 get_env(3m_unicode) len(3m_unicode) len(3m_unicode) NAME LEN(3f) - [M_unicode:WHITESPACE] Length of a string (LICENSE:MIT) SYNOPSIS result = len(string) elemental integer function len(string) type(unicode_type),intent(in) :: string CHARACTERISTICS • STRING is a scalar or array string variable • the returned value is of default INTEGER kind DESCRIPTION LEN(3) returns the length of a type(unicode_type) string. Note that unlike the intrinsic of the same name STRING needs to be defined; as the length of each element is not defined until allocated; and the KIND parameter is not available for specifying the kind of the integer returned. OPTIONS • STRING : A scalar or array string to return the length(s) of in glyph counts. If it is an unallocated allocatable variable or a pointer that is not associated, its length type parameter shall not be deferred. RESULT The result has a value equal to the number of glyphs in STRING if it is scalar or the elements of STRING if it is an array. EXAMPLES Sample program program demo_len use M_unicode, only : assignment(=), ut=>unicode_type, len use M_unicode, only : write(formatted) implicit none type(ut) :: string type(ut),allocatable :: many_strings(:) integer :: ii ! BASIC USAGE string='Noho me ka hau’oli' ! (Be happy.) ii=len(string) write(*,'(DT,*(g0))')string, ' LEN=', ii ! string=' How long is this allocatable string? ' write(*,'(DT,*(g0))')string, ' LEN=', len(string) ! ! STRINGS IN AN ARRAY MAY BE OF DIFFERENT LENGTHS many_strings = [ ut('Tom'), ut('Dick'), ut('Harry') ] write(*,'(*(g0,1x))')'length of elements of array=',len(many_strings) ! write(*,'(*(g0))')'length from type parameter inquiry=',string%len() ! ! LOOK AT HOW A PASSED STRING CAN BE USED ... call passed(ut(' how long? ')) ! contains ! subroutine passed(str) type(ut),intent(in) :: str ! you can query the length of the passed variable ! when an interface is present write(*,'(*(g0))')'length of passed value is ', len(str) end subroutine passed ! end program demo_len Results: > Noho me ka hau’oli LEN=18 > How long is this allocatable string? LEN=38 > length of elements of array= 3 4 5 > length from type parameter inquiry=38 > length of passed value is 11 SEE ALSO functions that perform operations on character strings: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • non-elemental: len_trim(3), len(3), repeat(3), trim(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 len(3m_unicode) len_trim(3m_unicode) len_trim(3m_unicode) NAME LEN_TRIM(3f) - [M_unicode:WHITESPACE] string length without trailing blank characters (LICENSE:MIT) SYNOPSIS result = len_trim(string) elemental integer(kind=kind) function len_trim(string) character(len=*),intent(in) :: string CHARACTERISTICS • string is of type type(unicode_type) • the return value is of type default integer. DESCRIPTION len_trim(3) returns the length of a string, ignoring any trailing blanks. OPTIONS • string : the input string whose length is to be measured. RESULT the result equals the number of glyphs remaining after any trailing blanks in string are removed. if the input argument is of zero length or all blanks the result is zero. EXAMPLES sample program program demo_len_trim use M_unicode, only : ut=>unicode_type, assignment(=) use M_unicode, only : len,len_trim use M_unicode, only : write(formatted) implicit none type(ut) :: string integer :: i ! basic usage string=" how long is this string? " print '(DT)', string print *, 'untrimmed length=',len(string) print *, 'trimmed length=',len_trim(string) ! ! print string, then print substring of string string='xxxxx ' write(*,'(*(DT))')string,string,string i=len_trim(string) print '(*(DT))',string%sub(1,i),string%sub(1,i),string%sub(1,i) ! ! elemental example ele:block ! an array of strings may be used type(ut),allocatable :: tablet(:) tablet=[ & & ut(' how long is this string? '),& & ut('and this one?')] write(*,*)'untrimmed length= ',len(tablet) write(*,*)'trimmed length= ',len_trim(tablet) write(*,*)'sum trimmed length=',sum(len_trim(tablet)) endblock ele ! end program demo_len_trim results: > how long is this string? > untrimmed length= 30 > trimmed length= 25 > xxxxx xxxxx xxxxx > xxxxxxxxxxxxxxx > untrimmed length= 30 13 > trimmed length= 25 13 > sum trimmed length= 38 SEE ALSO functions that perform operations on character strings, return lengths of arguments, and search for certain arguments: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • nonelemental: repeat(3), len(3), trim(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 len_trim(3m_unicode) lower(3m_unicode) lower(3m_unicode) NAME LOWER(3f) - [M_unicode:CASE] changes a string to lowercase over specified range (LICENSE:MIT) SYNOPSIS pure elemental function lower(str) result (string) type(unicode_type),intent(in) :: str type(unicode_type) :: string DESCRIPTION lower(str) returns a copy of the input string with all characters converted to miniscule (ie. "lowercase"). OPTIONS str string to convert to miniscule RETURNS lower copy of the entire input string with all characters converted to miniscule. TRIVIA The terms "uppercase" and "lowercase" date back to the early days of the mechanical printing press. Individual metal alloy casts of each needed letter or punctuation symbol were meticulously added to a press block, by hand, before rolling out copies of a page. These metal casts were stored and organized in wooden cases. The more-often-needed miniscule letters were placed closer to hand, in the lower cases of the work bench. The less often needed, capitalized, majuscule letters, ended up in the harder to reach upper cases. EXAMPLES Sample program: program demo_lower use iso_fortran_env, only : stdout => output_unit use M_unicode, only : lower, unicode_type, assignment(=), trim use M_unicode, only : ut => unicode_type, operator(==) implicit none character(len=*),parameter :: g='(*(g0))' type(unicode_type) :: pangram type(unicode_type) :: diacritics type(unicode_type) :: expected ! ! a sentence containing every letter of the English alphabet pangram="THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG" expected="the quick brown fox jumps over the lazy dog" call test(pangram,expected) ! ! Slovak pangram PANGRAM = 'VYPÄTÁ DCÉRA GRÓFA MAXWELLA S IQ NIŽŠÍM AKO & &KÔŇ NÚTI ČEĽAĎ HRÝZŤ HŔBU JABĹK.' expected = 'vypätá dcéra grófa maxwella s iq nižším ako & &kôň núti čeľaď hrýzť hŕbu jabĺk.' call test(pangram,expected) ! ! contains each special Czech letter with diacritics exactly once DIACRITICS='PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY.' expected ='příliš žluťoučký kůň úpěl ďábelské ódy.' print g,'("A horse that was too yellow-ish moaned devilish odes")' call test(diacritics,expected) contains subroutine test(in,expected) type(unicode_type),intent(in) :: in type(unicode_type),intent(in) :: expected type(unicode_type) :: lowercase character(len=*),parameter :: nl=new_line('A') write(stdout,g)in%character() lowercase=lower(in) write(stdout,g)lowercase%character() write(stdout,g)merge('PASSED','FAILED',lowercase == expected ),nl end subroutine test end program demo_lower Expected output > THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG > the quick brown fox jumps over the lazy dog > PASSED > > VYPÄTÁ DCÉRA GRÓFA MAXWELLA S IQ NIŽŠÍM AKO KÔŇ NÚTI ... > ČEĽAĎ HRÝZŤ HŔBU JABĹK. > vypätá dcéra grófa maxwella s iq nižším ako kôň núti ... > čeľaď hrýzť hŕbu jabĺk. > PASSED > > ("A horse that was too yellow-ish moaned devilish odes") > PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY. > příliš žluťoučký kůň úpěl ďábelské ódy. > PASSED AUTHOR John S. Urban LICENSE MIT May 05, 2026 lower(3m_unicode) M_unicode(3m_unicode) M_unicode(3m_unicode) NAME M_unicode(3f) - [M_unicode::INTRO] Unicode string module (LICENSE:MIT) DESCRIPTION The M_unicode(3f) module is a collection of Fortran string methods that work with UTF-8 encoded text not just ASCII-7 data. Strings are declared using the user-defined type "UNICODE_TYPE". The type supports allocatable ragged arrays where each element may be of differing length. Compiler support of the optional Fortran ISO_10646 extension is not required. The M_unicode(3) module overloads the Fortran built-in CHARACTER intrinsics and operators to allow TYPE(UNICODE_TYPE) to use the intrinsic procedure names in much the same manner the intrinsics are used with CHARACTER variables. The intrinsic overloads include TOKENIZE(3) and SPLIT(3) even if the underlying compiler does not yet support those intrinsics. Overloads of assignment, logical comparisons, and concatenation using the // operator with strings (and other types) are included as well to make use of TYPE(UNICODE_TYPE) largely consistent with standard CHARACTER string manipulations. Nearly all the methods are available using both OOP and procedural syntax. In addition M_unicode(3) includes routines for parsing, tokenizing, changing case, substituting new strings for substrings, locating strings with simple wildcard expressions, removing tabs and line terminators and other advanced non-intrinsic string manipulations. The **UPPER()** and **LOWER()** functions support the concept of case for the Unicode Latin characters not just the ASCII subset, and a basic SORT() function provides for ordering the data by Unicode codepoint values. A PAD() function allows padding strings at least up to a specified glyph length with a repeating pattern. **M_unicode** should be useful for anyone working with UTF-8 data, particularly if the compiler does not support the UCS-4 extensions of Fortran. Until proven otherwise M_unicode(3) should work with any environment where UTF-8 files are supported. The type components are not public to allow for use of the same user code when using other modules such as M_ucs4(3) which ultimately will provide the same user interface but internally using ISO_10646 internal encoding instead of an array of integers containing codepoints (which is what M_unicode(3) uses). This has the drawback of not permitting easy use of array syntax directly on the codepoint array. Perhaps this decision will change but in the meantime several methods such as REPLACE(3) and CHARACTER(3) and SUB(3) provide similar functionality. SYNOPSIS public methods: TOKENS split subroutine parses string using specified delimiter characters into tokens tokenize Parse a string into tokens. EDITING replace function non-recursively globally replaces old substring with new substring transliterate replace characters from old set with new set pound_to_box create simple boxes using pound character add_border add border to an array of strings reverse reverse order of glyphs on a line CASE upper function converts string to uppercase lower function converts string to miniscule STRING LENGTH len return the length of a string in glyphs len_trim find location of last non-whitespace glyph PADDING pad pad string to at least specified length with pattern string repeat Repeated string concatenation WHITE SPACE trim Remove trailing blank characters of a string expandtabs expand tab characters adjustl Left adjust a string adjustr Right adjust a string ENCODING character(STRING,start,end,inc) converts a string to type CHARACTER. escape expand C-like escape strings add_backslash replace other than printable ASCII-7 characters with C-like escape strings expand_html expand html "&NAME;" escape strings codepoints_to_utf8(codepoints,utf8,nerr) subroutine to convert codepoints to UTF-8 bytes utf8_to_codepoints(utf8,codepoints,nerr) subroutine to convert UTF-8 bytes to codepoints STRING%character(start,end,inc) OOP syntax for converting a string to type CHARACTER. STRING%byte(start,end,inc) Convert to an array of CHARACTER(len=1) bytes. STRING%codepoint(start,end,inc) converts a string to an INTEGER array of Unicode codepoints char converts an integer codepoint into a string ichar converts a type(unicode_type) glyph into an integer codepoint NUMERIC STRINGS fmt convert intrinsic numeric value to string using optional format CHARACTER TESTS glob compares given string for match to pattern which may contain wildcard characters ! the following are based on Unicode codepoint, not dictionary order lgt Lexical greater than lge Lexical greater than or equal leq Lexical equal lne Lexical not equal lle Lexical less than or equal llt Lexical less than QUERY isascii checks whether string is composed of all character values that fit into the ASCII-7 character set. isblank returns .true. if string is composed of all blanks (spaces or from the set of Unicode blanks or a horizontal tab). isspace returns .true. if string is composed of all spaces (ASCII-7 spaces or from the set of Unicode blanks). IO readline read a text line from a file slurp read formatted UTF-8 encoded file into TYPE(UNICODE_TYPE) array LOCATION index glyph position of a substring within a string scan Scan a string for the presence of a set of characters verify Scan a string for the absence of a set of characters CONCATENATION join join elements of an array into a single string operator(.cat.), operator(//) concatenate strings and/or convert intrinsics to strings and concatenate SYSTEM get_env Get environment variable get_arg Get command line argument SORT sort Sort by Unicode codepoint value (not dictionary order) BASE CONVERSION QUOTES NONALPHA OOPS INTERFACE An OOP (Object-Oriented Programming) interface to the M_unicode(3fm) module provides an alternative interface to all the same procedures except for SORT(3f) and CHAR(3f). SEE ALSO All the procedure descriptions are conglomerated into the single file "manual.txt" for simple access not requiring access to man-pages or browsers. There are additional routines in other GPF modules for working with expressions (M_calculator), time strings (M_time), random strings (M_random, M_uuid), lists (M_list), and interfacing with the C regular expression library (M_regex). EXAMPLES Each of the procedures includes an example program in the example/ directory as well as a corresponding man(1) page for the procedure. Sample program: program demo_M_unicode use,intrinsic :: iso_fortran_env, only : stdout=>output_unit use M_unicode,only : tokenize, replace, character, upper, lower, len use M_unicode,only : unicode_type, assignment(=), operator(//) use M_unicode,only : ut => unicode_type, ch => character use M_unicode,only : write(formatted) type(unicode_type) :: string type(unicode_type) :: numeric, uppercase, lowercase type(unicode_type),allocatable :: array(:) character(len=*),parameter :: all='(g0)' !character(len=*),parameter :: uni='(DT)' uppercase='АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ' lowercase='абвгґдеєжзиіїйклмнопрстуфхцчшщьюя' numeric='0123456789' ! string=uppercase//numeric//lowercase ! print all, 'Original string:' print all, ch(string) print all, 'length in bytes :',len(string%character()) print all, 'length in glyphs:',len(string) print all ! print all, 'convert to all uppercase:' print all, ch( UPPER(string) ) print all ! print all, 'convert to all lowercase:' print all, ch( string%LOWER() ) print all ! print all, 'tokenize on spaces ... ' call TOKENIZE(string,ut(' '),array) print all, '... writing with A or G format:',character(array) !print uni, ut('... writing with DT format'),array print all ! print all, 'case-insensitive replace:' print all, ch( & & REPLACE(string, & & ut('клмнопрс'), & & ut('--------'), & & ignorecase=.true.) ) ! print all ! end program demo_M_unicode Results: > Original string: > АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ0123456789 ... > абвгґдеєжзиіїйклмнопрстуфхцчшщьюя > length in bytes : > 144 > length in glyphs: > 78 > > convert to all uppercase: > АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ0123456789 ... > АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ > > > tokenize on spaces ... > ... writing with A or G format: > АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ > 0123456789 > абвгґдеєжзиіїйклмнопрстуфхцчшщьюя > ... writing with DT format > АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ > 0123456789 > абвгґдеєжзиіїйклмнопрстуфхцчшщьюя > > case-insensitive replace: > АБВГҐДЕЄЖЗИІЇЙ--------ТУФХЦЧШЩЬЮЯ0123456789 ... > абвгґдеєжзиіїй--------туфхцчшщьюя AUTHOR John S. Urban LICENSE MIT May 05, 2026 M_unicode(3m_unicode) pad(3m_unicode) pad(3m_unicode) NAME PAD(3f) - [M_unicode:PAD] return string padded to at least specified length (LICENSE:MIT) SYNOPSIS function pad(str,length,pattern,right,clip) result(out) type(unicode_type) :: str integer,intent(in) :: length type(unicode_type) :: out type(unicode_type),intent(in),optional :: pattern logical,intent(in),optional :: right logical,intent(in),optional :: clip DESCRIPTION pad(3f) pads a string with a pattern to at least the specified length. If the trimmed input string is longer than the requested length the trimmed string is returned. OPTIONS str the input string to return trimmed, but then padded to the specified length if shorter than length length The minimum string length to return pattern optional string to use as padding. Defaults to a space. right if true pads string on the right, else on the left. Defaults to true. clip trim spaces from input string ends. Defaults to .true. RETURNS out The input string padded to the requested length or the trimmed input string if the input string is longer than the requested length. EXAMPLES Sample Program: program demo_pad use M_unicode, only : pad, assignment(=) !use M_unicode, only : write(formatted) use M_unicode, only : len use M_unicode, only : ch=> character use M_unicode, only : ut=> unicode_type implicit none type(ut) :: string type(ut) :: answer integer :: i !character(len=*),parameter :: u='(*(DT))' character(len=*),parameter :: u='(*(g0))' ! string='abcdefghij' ! write(*,*)'pad on right till 20 characters long' answer=pad(string,20) write(*,'("[",g0,"]",/)') answer%character() ! write(*,*)'original is not trimmed for short length requests' answer=pad(string,5) write(*,'("[",g0,"]",/)') answer%character() ! i=30 write(*,*)'pad with specified string and left-justified integers' write(*,'(1x,g0,1x,i0)') & & ch(pad(ut('CHAPTER 1 : The beginning '),i,ut('.') )), 1 , & & ch(pad(ut('CHAPTER 2 : The end '),i,ut('.') )), 1234, & & ch(pad(ut('APPENDIX '),i,ut('.') )), 1235 ! write(*,*)'pad with specified string and right-justified integers' write(*,'(1x,g0,i7)') & & ch(pad(ut('CHAPTER 1 : The beginning '),i,ut('.') )), 1 , & & ch(pad(ut('CHAPTER 2 : The end '),i,ut('.') )), 1234, & & ch(pad(ut('APPENDIX '),i,ut('.') )), 1235 ! write(*,*)'pad on left with zeros' write(*,u)ch(pad(ut('12'),5,ut('0'),right=.false.)) ! write(*,*)'various lengths with clip .true. and .false.' write(*,u)ch(pad(ut('12345 '),30,ut('_'),right=.false.)) write(*,u)ch(pad(ut('12345 '),30,ut('_'),right=.false.,clip=.true.)) write(*,u)ch(pad(ut('12345 '), 7,ut('_'),right=.false.)) write(*,u)ch(pad(ut('12345 '), 7,ut('_'),right=.false.,clip=.true.)) write(*,u)ch(pad(ut('12345 '), 6,ut('_'),right=.false.)) write(*,u)ch(pad(ut('12345 '), 6,ut('_'),right=.false.,clip=.true.)) write(*,u)ch(pad(ut('12345 '), 5,ut('_'),right=.false.)) write(*,u)ch(pad(ut('12345 '), 5,ut('_'),right=.false.,clip=.true.)) write(*,u)ch(pad(ut('12345 '), 4,ut('_'),right=.false.)) write(*,u)ch(pad(ut('12345 '), 4,ut('_'),right=.false.,clip=.true.)) end program demo_pad Results: > pad on right till 20 characters long > [abcdefghij ] > > original is not trimmed for short length requests > [abcdefghij] > > pad with specified string and left-justified integers > CHAPTER 1 : The beginning .... 1 > CHAPTER 2 : The end .......... 1234 > APPENDIX ..................... 1235 > pad with specified string and right-justified integers > CHAPTER 1 : The beginning .... 1 > CHAPTER 2 : The end .......... 1234 > APPENDIX ..................... 1235 > pad on left with zeros > 00012 > various lengths with clip .true. and .false. > ________________________12345 > _________________________12345 > _12345 > __12345 > 12345 > _12345 > 12345 > 12345 > 12345 > 2345 SEE ALSO adjustl(3f), adjustr(3f), repeat(3f), trim(3f), len_trim(3f), len(3f) AUTHOR John S. Urban LICENSE MIT May 05, 2026 pad(3m_unicode) pound_to_box(3m_unicode) pound_to_box(3m_unicode) NAME pound_to_box(3f) - [M_unicode:EDITING] convert pound character to box characters (LICENSE:MIT) SYNOPSIS syntax: function pound_to_box(win,style) result(winout) type(unicode_type)|character(len=*),intent(in) :: win(:) type(unicode_type)|character(len=*),intent(in),optional :: style type(unicode_type),allocatable :: winout(:) CHARACTERISTICS • WINOUT elements will all have the length of the longest element of WIN DESCRIPTION The pound character ("#") may be used to construct boxed text with the restriction that lines must be seperated by at least one character from other lines. OPTIONS win input array to be changed style may be "light", "bold", or "double". Default is "bold". RETURNS winout an array of strings with box characters substituted for adjacent pound characters. EXAMPLES Sample Program: program demo_pound_to_box use M_unicode, only : ut=>unicode_type use M_unicode, only : operator(//) use M_unicode, only : assignment(=) use M_unicode, only : character, pound_to_box implicit none type(ut),allocatable :: textout(:) character(len=*),parameter :: text(*)=[character(len=80) :: & '############################################', & '#abcdefg# What about # # # #', & '#hijklmn# this text? # # ######', & '############################### # #', & '# # # # ######', & '# # # # # #', & '############################################', & '', & ' ###################################', & ' # WARNING, WARNING, Will Robinson #', & ' ###################################'] textout=text call write_text() textout=pound_to_box(text) call write_text() textout=pound_to_box(text,style='light') call write_text() textout=pound_to_box(text,style='double') call write_text() contains subroutine write_text() integer :: i write(*,'(*(a:))',advance='no') & & (trim(textout(i)%character()), & & new_line('a'), & & i=1,size(textout)) end subroutine write_text end program demo_pound_to_box Results: > ############################################ > #abcdefg# What about # # # # > #hijklmn# this text? # # ###### > ############################### # # > # # # # ###### > # # # # # # > ############################################ > > ################################### > # WARNING, WARNING, Will Robinson # > ################################### > ┏━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━┓ > ┃abcdefg┃ What about ┃ ┃ ┃ ┃ > ┃hijklmn┃ this text? ┃ ┃ ┣━━━━┫ > ┣━━━━━━━┻━━━━━━┳━━━━━╋━━━━━━━━┫ ┃ ┃ > ┃ ┃ ┃ ┃ ┣━━━━┫ > ┃ ┃ ┃ ┃ ┃ ┃ > ┗━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━┻━━━━━━━┻━━━━┛ > > ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ > ┃ WARNING, WARNING, Will Robinson ┃ > ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ > ┌───────┬────────────┬────────┬───────┬────┐ > │abcdefg│ What about │ │ │ │ > │hijklmn│ this text? │ │ ├────┤ > ├───────┴──────┬─────┼────────┤ │ │ > │ │ │ │ ├────┤ > │ │ │ │ │ │ > └──────────────┴─────┴────────┴───────┴────┘ > > ┌─────────────────────────────────┐ > │ WARNING, WARNING, Will Robinson │ > └─────────────────────────────────┘ > ╔═══════╦════════════╦════════╦═══════╦════╗ > ║abcdefg║ What about ║ ║ ║ ║ > ║hijklmn║ this text? ║ ║ ╠════╣ > ╠═══════╩══════╦═════╬════════╣ ║ ║ > ║ ║ ║ ║ ╠════╣ > ║ ║ ║ ║ ║ ║ > ╚══════════════╩═════╩════════╩═══════╩════╝ > > ╔═════════════════════════════════╗ > ║ WARNING, WARNING, Will Robinson ║ > ╚═════════════════════════════════╝ AUTHOR John S. Urban LICENSE MIT May 05, 2026 pound_to_box(3m_unicode) readline(3m_unicode) readline(3m_unicode) NAME READLINE(3f) - [M_unicode:IO] read a line from specified LUN into string up to line length limit (LICENSE:MIT) SYNTAX function readline(lun,iostat) result(line) integer,intent(in),optional :: lun integer,intent(out),optional :: iostat type(unicode_type) :: line DESCRIPTION Read a line of any length up to programming environment maximum line length. Requires Fortran 2003+. It is primarily expected to be used when reading input which will then be parsed. The input file must have a PAD attribute of YES for the function to work properly, which is typically true. The simple use of a loop that repeatedly re-allocates a character variable in addition to reading the input file one buffer at a time could (depending on the programming environment used) be inefficient, as it could reallocate and allocate memory used for the output string with each buffer read. OPTIONS LUN optional LUN (Fortran logical I/O unit) number. Defaults to stdin. IOSTAT status returned by READ(IOSTAT=IOS). If not zero, an error occurred or an end-of-file or end-of-record was encountered. RETURNS LINE line read. if IOSTAT is not zero, LINE returns the I/O error message. EXAMPLE Sample program: program demo_readline use,intrinsic :: iso_fortran_env, only : stdin=>input_unit use,intrinsic :: iso_fortran_env, only : iostat_end use M_unicode, only : readline, len, trim use M_unicode, only : assignment(=), ch=>character, ut=>unicode_type implicit none type(ut) :: ln character(len=:),allocatable :: aline integer,allocatable :: ints(:) integer :: iostat, lun, i character(len=*),parameter :: filedata(*)=[character(len=80) :: & 'The famous Confucian expression:', & '', & ' "己所不欲,勿施於人"', & ' (jǐ suǒ bù yù, wù shī yú rén)', & 'or', & ' "What you do not want done to yourself,', & ' do not do to others":'] ! create a scratch file to read open(newunit=lun,status='scratch',pad='yes') write(lun,'(a)')(trim(filedata(i)),i=1,size(filedata)) !---------------------------------------------------------------- ! read back UTF-8 byte stream and show the lines read rewind(unit=lun) do ln=readline(lun,iostat=iostat) if(iostat.ne.0)exit ! write the glyph length, byte length, line in brackets write(*,'(i4,1x,i4,1x,a)')len(ln),len(ch(ln)),'['//ch(ln)//']' enddo call checkit() !---------------------------------------------------------------- ! the same thing except convert to default intrinsic types rewind(unit=lun) do ln=readline(lun,iostat=iostat) if(iostat.ne.0)exit ! assign the string to an allocatable array of integers ints=ln ! and the string to a character variable aline=ln write(*,'(i4,1x,i4,1x,a)')len(ln),len(aline),'['//ch(ln)//']' enddo call checkit() !---------------------------------------------------------------- ! again but this time show the lines as Unicode codepoints rewind(unit=lun) do ln=readline(lun,iostat=iostat) if(iostat.ne.0)exit if(len(ln).eq.0)then write(*,'(/,10(g0,1x)," ...")')[0,0] else write(*,'(/,10(g0,1x)," ...")')ln%codepoint() endif enddo call checkit() !---------------------------------------------------------------- ! show the line as Unicode codepoints using default integer array rewind(unit=lun) do ln=readline(lun,iostat=iostat) if(iostat.ne.0)exit ! assign the string to an allocatable array of integers ints=ln if(size(ints).eq.0)ints=[0,0] write(*,'(/,10(g0,1x)," ...")')ints enddo call checkit() !---------------------------------------------------------------- contains subroutine checkit() if(iostat /= iostat_end)then write(*,*)'error reading input:',ch(trim(ln)) endif write(*,*) end subroutine checkit end program demo_readline AUTHOR John S. Urban LICENSE MIT May 05, 2026 readline(3m_unicode) repeat(3m_unicode) repeat(3m_unicode) NAME REPEAT(3) - [M_unicode:PAD] Repeated string concatenation (LICENSE:MIT) SYNOPSIS result = repeat(string, ncopies) type(unicode_type) function repeat(string, ncopies) type(unicode_type),intent(in) :: string integer(kind=**),intent(in) :: ncopies CHARACTERISTICS • STRING is a scalar string of type(unicode_type). • NCOPIES is a scalar integer. • the result is a new scalar string of type type(unicode_type) DESCRIPTION REPEAT(3) concatenates copies of a string. OPTIONS • STRING : The input string to repeat • NCOPIES : Number of copies to make of STRING, greater than or equal to zero (0). RESULT A new string built up from NCOPIES copies of STRING. EXAMPLES Sample program: program demo_repeat use M_unicode, only : ut=>unicode_type,repeat,escape,write(formatted) implicit none write(*,'(DT)') repeat(escape("\u2025*"), 35) write(*,'(DT)') repeat(ut("_"), 70) ! line break write(*,'(DT)') repeat(ut("1234567890"), 7) ! number line write(*,'(DT)') repeat(ut(" |"), 7) ! end program demo_repeat STANDARD Fortran 95 SEE ALSO Functions that perform operations on character strings: • ELEMENTAL: ADJUSTL(3), ADJUSTR(3), INDEX(3), SCAN(3), VERIFY(3) • NON-ELEMENTAL: LEN_TRIM(3), LEN(3), REPEAT(3), TRIM(3) Fortran descriptions (license: MIT) @urbanjost LICENSE MIT May 05, 2026 repeat(3m_unicode) replace(3m_unicode) replace(3m_unicode) NAME REPLACE(3f) - [M_unicode:EDITING] function replaces one substring for another in string (LICENSE:MIT) SYNOPSIS syntax: impure elemental function replace(target,old,new, & & occurrence, & & repeat, & & ignorecase, & & ierr,back) result (newline) or function replace(target,start,end,new) result newline type(unicode_type)|character(len=*),intent(in) :: target type(unicode_type)|character(len=*),intent(in) :: old type(unicode_type)|character(len=*),intent(in) :: new or type(unicode_type)|character(len=*),intent(in) :: new integer, intent(in) :: start integer, intent(in) :: end integer,intent(in),optional :: occurrence integer,intent(in),optional :: repeat logical,intent(in),optional :: ignorecase integer,intent(out),optional :: changes logical,intent(in),optional :: back character(len=:),allocatable :: newline CHARACTERISTICS • TARGET,OLD and NEW may be a string or a character variable. DESCRIPTION Replace old substring with new value in string. Either a old and new string is specified, or a new string and a column range indicating the position of the text to replace is specified. OPTIONS target input line to be changed old old substring to replace new new substring start starting column of text to replace end ending column of text to replace KEYWORD REQUIRED occurrence if present, start changing at the Nth occurrence of the OLD string. repeat number of replacements to perform. Defaults to a global replacement. ignorecase whether to ignore ASCII case or not. Defaults to .false. . back if true start replacing moving from the right end of the string moving left instead of from the left to the right. RETURNS newline allocatable string returned changes count of changes made. EXAMPLES Sample Program: program demo_replace use M_unicode, only : ut=>unicode_type use M_unicode, only : unicode_type use M_unicode, only : character, replace use M_unicode, only : write(formatted) implicit none type(unicode_type) :: line ! write(*,'(DT)') & & replace(ut('Xis is Xe string'),ut('X'),ut('th') ) write(*,'(DT)') & & replace(ut('Xis is xe string'),ut('x'),ut('th'),ignorecase=.true.) write(*,'(DT)') & & replace(ut('Xis is xe string'),ut('X'),ut('th'),ignorecase=.false.) ! ! a null old substring means "at beginning of line" write(*,'(DT)') & & replace(ut('my line of text'),ut(''),ut('BEFORE:')) ! ! a null new string deletes occurrences of the old substring write(*,'(DT)') replace(ut('I wonder i ii iii'),ut('i'),ut('')) ! ! Examples of the use of RANGE ! line=replace(ut('aaaaaaaaa'),ut('a'),ut('A'),occurrence=1,repeat=1) write(*,*)'replace first a with A ['//line%character()//']' ! line=replace(ut('aaaaaaaaa'),ut('a'),ut('A'),occurrence=3,repeat=3) write(*,*)'replace a with A for 3rd to 5th occurrence [' & & //line%character()//']' ! line=replace(ut('ababababa'),ut('a'),ut(''),occurrence=3,repeat=3) write(*,*)'replace a with null instances 3 to 5 ['// & & line%character()//']' ! line=replace( & & ut('a b ab baaa aaaa aa aa a a a aa aaaaaa'),& & ut('aa'),ut('CCCC'),occurrence=-1,repeat=1) write(*,*)'replace lastaa with CCCC ['//line%character()//']' ! write(*,'(DT)')replace(ut('myf90stuff.f90.f90'),& & ut('f90'),ut('for'),occurrence=-1,repeat=1) write(*,'(DT)')replace(ut('myf90stuff.f90.f90'),& & ut('f90'),ut('for'),occurrence=-2,repeat=2) ! end program demo_replace Results: > this is the string > this is the string > this is xe string > BEFORE:my line of text > I wonder > replace first a with A [Aaaaaaaaa] > replace a with A for 3rd to 5th occurrence [aaAAAaaaa] > replace a with null instances 3 to 5 [ababbb] > replace lastaa with CCCC [a b ab baaa aaaa aa aa a a a aa aaaaCCCC] > myf90stuff.f90.for > myforstuff.for.f90 AUTHOR John S. Urban LICENSE MIT May 05, 2026 replace(3m_unicode) reverse(3m_unicode) reverse(3m_unicode) NAME reverse(3f) - [M_unicode:CASE] reverse order of glyphs on a line (LICENSE:MIT) SYNOPSIS impure elemental function reverse(str) result (string) type(unicode_type),intent(in) :: str type(unicode_type) :: string DESCRIPTION reverse(string) returns a copy of the input string with all characters in reverse position on the line. OPTIONS str string to reverse RETURNS reverse copy of the input string with order of characters on the line reversed. EXAMPLES Sample program: program demo_reverse use iso_fortran_env, only : stdout => output_unit use M_unicode, only : reverse, ch=>character use M_unicode, only : unicode_type, assignment(=) use M_unicode, only : ut => unicode_type, operator(==) implicit none character(len=*),parameter :: g='(g0)' type(unicode_type) :: original(3) original(1)='abcde' original(2)='한국말' original(3)='五十七' write(stdout,g)ch(original) write(stdout,*) write(stdout,g)ch(reverse(original)) end program demo_reverse Expected output > abcde > 한국말 > 五十七 > edcba > 말국한 > 七十五 AUTHOR John S. Urban LICENSE MIT May 05, 2026 reverse(3m_unicode) scan(3m_unicode) scan(3m_unicode) NAME SCAN(3f) - [M_unicode:SEARCH] Scan a string for the presence of a set of characters (LICENSE:MIT) SYNOPSIS result = scan( string, set, [,back] ) elemental integer(kind=KIND) function scan(string,set,back) type(unicode_type),intent(in) :: string type(unicode_type),intent(in) :: set or character(len=*),intent(in) :: set logical,intent(in),optional :: back CHARACTERISTICS • STRING is a string of type unicode_type • SET must be a string of type unicode_type or character • BACK is a logical of default kind • the result is an integer of default kind. DESCRIPTION SCAN(3) scans a STRING for any of the characters in a SET of characters. If BACK is either absent or equals .false., this function returns the position of the leftmost character of STRING that is in SET. If BACK equals .true., the rightmost position is returned. If no character of SET is found in STRING, the result is zero. OPTIONS • STRING : the string to be scanned • SET : the set of characters which will be matched • BACK : if .true. the position of the rightmost character matched is returned, instead of the leftmost. RESULT If BACK is absent or is present with the value false and if STRING contains at least one character that is in SET, the value of the result is the position of the leftmost character of STRING that is in SET. If BACK is present with the value true and if STRING contains at least one character that is in SET, the value of the result is the position of the rightmost character of STRING that is in SET. The value of the result is zero if no character of STRING is in SET or if the length of STRING or SET is zero. EXAMPLES Sample program: program demo_scan use iso_fortran_env, only : stdout => output_unit use M_unicode, only : scan, unicode_type, assignment(=) use M_unicode, only : ut=>unicode_type implicit none character(len=*),parameter :: g='(*(g0,1x))' type(ut) :: line type(ut) :: set ! write(*,*) scan("fortran", "ao") ! 2, found ’o’ write(*,*) scan("fortran", "ao", .true.) ! 6, found ’a’ write(*,*) scan("fortran", "c++") ! 0, found none ! line='parsley😃sage😃rosemary😃😃thyme' set='😃' write(stdout,g) '12345678901234567890123456789012345678901234567890' write(stdout,g) line%character() write(stdout,g) scan(line, set) write(stdout,g) scan(line, set, back=.true.) write(stdout,g) scan(line, set, back=.false.) write(stdout,g) scan(line, unicode_type("NOT")) write(stdout,g) 'OOP' write(stdout,g) line%scan(set) write(stdout,g) line%scan(ut("o")) end program demo_scan Results: > 2 > 6 > 0 > 12345678901234567890123456789012345678901234567890 > parsley😃sage😃rosemary😃😃thyme > 8 > 23 > 8 > 0 > OOP > 8 > 15 SEE ALSO Functions that perform operations on character strings, return lengths of arguments, and search for certain arguments: • ADJUSTL(3), ADJUSTR(3), INDEX(3), VERIFY(3) • LEN_TRIM(3), LEN(3), REPEAT(3), TRIM(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 scan(3m_unicode) slurp(3m_unicode) slurp(3m_unicode) NAME slurp(3f) - [M_unicode:READ] read formatted UTF-8 file into a TYPE(UNICODE_TYPE) string array (LICENSE:MIT) SYNOPSIS function slurp(filename,iomsg) result(text) character(len=*),intent(in),optional :: filename ! or type(unicode_type),intent(in),optional :: filename character(len=*),intent(out),optional,allocatable :: iomsg type(unicode_type),allocatable,intent(out) :: text(:) DESCRIPTION Uses READLINE(3) to read an entire formatted UTF-8 encoded file into a TYPE(UNICODE_TYPE) string array, each line of the file becoming an element of the output array. NOTE Never casually read an entire file into memory if you can process it per line or in smaller units; as large files can consume unreasonable amounts of memory. OPTIONS filename filename to read into memory. If the filename is absent stdin will be read. iomsg if an error occurs iomsg will contain an error message, else it will be a null string. RETURNS text array of characters that holds contents of the file EXAMPLES Sample program, which creates test input file "inputfile" and then reads it back in. program demo_slurp use M_unicode, only : slurp, ut=>unicode_type use M_unicode, only : add_backslash, escape use M_unicode, only : assignment(=) implicit none type(ut),allocatable :: text(:) integer :: i character(len=:),allocatable :: iomsg character(len=*),parameter :: FILENAME='._inputfile' call create_test_file() text=slurp(FILENAME,iomsg=iomsg) if(iomsg.ne.'')then write(*,*)'*demo_slurp* failed to load file '//FILENAME write(*,*) iomsg else ! write out slurped data call write_text() ! encode with escape sequences and write data again do i=1,size(text) text(i)=add_backslash(text(i)) enddo call write_text() ! deencode escape sequences and write data again do i=1,size(text) text(i)=escape(text(i)) enddo call write_text() ! teardown deallocate(text) ! release memory open(file=FILENAME,unit=10) close(unit=10,status='delete') endif contains subroutine write_text() write(*,'(a)')repeat('=',80) write(*,'(*(a:))')(text(i)%character(),new_line('a'),i=1,size(text)) end subroutine write_text subroutine create_test_file() ! create test file open(file=FILENAME,unit=10,action='write') ! (Used by Microsoft Office as sample text for Croatian language.) write( *,'(a)')'Croation pangram:' write( *,'(a)')'' write(10,'(a)')'Gojazni đačić s biciklom drži hmelj i finu' write(10,'(a)')'vatu u džepu nošnje.' write(10,'(a)')'' write( *,'(a)')'The overweight little schoolboy with a bike is holding' write( *,'(a)')'hops and fine cotton in the pocket of his attire.' close(unit=10) end subroutine create_test_file end program demo_slurp Results: > Croation pangram: > > The overweight little schoolboy with a bike is holding > hops and fine cotton in the pocket of his attire. > > Gojazni đačić s biciklom drži hmelj i finu > vatu u džepu nošnje. > > Gojazni \u0111a\u010Di\u0107 s biciklom dr\u017Ei hmelj i finu > vatu u d\u017Eepu no\u0161nje. > > Gojazni đačić s biciklom drži hmelj i finu > vatu u džepu nošnje. > AUTHOR John S. Urban LICENSE MIT May 05, 2026 slurp(3m_unicode) sort(3m_unicode) sort(3m_unicode) NAME SORT(3f) - [M_unicode:SORT] indexed hybrid quicksort of an array (LICENSE:MIT) SYNOPSIS subroutine sort(data,index) type(unicode_type),intent(in) :: data(:) integer,intent(out) :: indx(size(data)) DESCRIPTION A rank hybrid quicksort. The data is not moved. An integer array is generated instead with values that are indices to the sorted order of the data. This requires a second array the size of the input array, which for large arrays would require a significant amount of memory. One major advantage of this method is that the indices can be used to access an entire user-defined type in sorted order. This makes this seemingly simple sort procedure usable with the vast majority of user-defined types. or other correlated data. BACKGROUND From Leonard J. Moss of SLAC: Here's a hybrid QuickSort I wrote a number of years ago. It's based on suggestions in Knuth, Volume 3, and performs much better than a pure QuickSort on short or partially ordered input arrays. This routine performs an in-memory sort of the first N elements of array DATA, returning into array INDEX the indices of elements of DATA arranged in ascending order. Thus, DATA(INDX(1)) will be the smallest number in array DATA; DATA(INDX(N)) will be the largest number in DATA. The original data is not physically rearranged. The original order of equal input values is not necessarily preserved. sort(3f) uses a hybrid QuickSort algorithm, based on several suggestions in Knuth, Volume 3, Section 5.2.2. In particular, the "pivot key" [my term] for dividing each subsequence is chosen to be the median of the first, last, and middle values of the subsequence; and the QuickSort is cut off when a subsequence has 9 or fewer elements, and a straight insertion sort of the entire array is done at the end. The result is comparable to a pure insertion sort for very short arrays, and very fast for very large arrays (of order 12 micro-sec/element on the 3081K for arrays of 10K elements). It is also not subject to the poor performance of the pure QuickSort on partially ordered data. Complex values are sorted by the magnitude of sqrt(r**2+i**2). • Created: sortrx(3f): 15 Jul 1986, Len Moss • saved from url=(0044)http://www.fortran.com/fortran/quick_sort2.f • changed to update syntax from F77 style; John S. Urban 20161021 • generalized from only real values to include other intrinsic types; John S. Urban 20210110 • type(unicode_type) version JSU 2025-09-20. See M_sort for other types. EXAMPLES Sample usage: program demo_sort use iso_fortran_env, only : stdout => output_unit use M_unicode, only : sort, unicode_type, assignment(=) use M_unicode, only : ut=>unicode_type, write(formatted) use M_unicode, only : ch=>character implicit none character(len=*),parameter :: g='(*(g0,1x))' integer,parameter :: isz=4 type(unicode_type) :: rr(isz) integer :: ii(isz) integer :: i ! write(stdout,g)'sort array with sort(3f)' rr=[ & ut("the"), & ut("quick"), & ut("brown"), & ut("fox") ] ! write(stdout,g)'original order' write(stdout,g)ch(rr) ! call sort(rr,ii) ! write(stdout,g)'sorted order' ! convert to character do i=1,size(rr) write(stdout,'(i3.3,1x,a)')i,rr(ii(i))%character() enddo ! write(stdout,g)'reorder original' rr=rr(ii) write(stdout,g)ch(rr) end program demo_sort Results: > sort array with sort(3f) > original order > the quick brown fox > sorted order > 001 brown > 002 fox > 003 quick > 004 the > reorder original > brown fox quick the AUTHOR John S. Urban LICENSE MIT May 05, 2026 sort(3m_unicode) split(3m_unicode) split(3m_unicode) NAME SPLIT(3f) - [M_unicode:PARSE] parse a string into tokens, one at a time. (LICENSE:MIT) SYNOPSIS call split (string, set, pos [, back]) type(unicode_type),intent(in) :: string type(unicode_type),intent(in) :: set integer,intent(inout) :: pos logical,intent(in),optional :: back CHARACTERISTICS • STRING is a scalar character variable • SET is a scalar string variable DESCRIPTION Find the extent of consecutive tokens in a string. given a string and a position to start looking for a token return the position of the end of the token. a set of separator characters may be specified as well as the direction of parsing. typically consecutive calls are used to parse a string into a set of tokens by stepping through the start and end positions of each token. OPTIONS • STRING : the string to search for tokens in. • SET : Each character in set is a token delimiter. a sequence of zero or more characters in string delimited by any token delimiter, or the beginning or end of string, comprise a token. thus, two consecutive token delimiters in STRING, or a token delimiter in the first or last character of STRING, indicate a token with zero length. • POS : on input, the position from which to start looking for the next separator from. This is typically the first character or the last returned value of POS if searching from left to right (ie. back is absent or .true.) or the last character or the last returned value of POS when searching from right to left (ie. when back is .FALSE.). If BACK is present with the value .TRUE., the value of pos shall be in the range 0 < POS <= len(STRING)+1; otherwise it shall be in the range 0 <= POS <= len(STRING). So POS on input is typically an end of the string or the position of a separator, probably from a previous call to split but POS on input can be any position in the range 1 <= POS <= len(STRING). if POS points to a non-separator character in the string the call is still valid but it will start searching from the specified position and that will result (somewhat obviously) in the string from POS on input to the returned POS being a partial token. • BACK : If BACK is absent or is present with the value .FALSE., POS is assigned the position of the leftmost token delimiter in string whose position is greater than POS, or if there is no such character, it is assigned a value one greater than the length of string. this identifies a token with starting position one greater than the value of POS on invocation, and ending position one less than the value of POS on return. If BACK is present with the value .TRUE., POS is assigned the position of the rightmost token delimiter in string whose position is less than POS, or if there is no such character, it is assigned the value zero. This identifies a token with ending position one less than the value of POS on invocation, and starting position one greater than the value of POS on return. EXAMPLE sample program: program demo_split use iso_fortran_env, only : stdout => output_unit use M_unicode, only : unicode_type, assignment(=) use M_unicode, only : split, len, character use M_unicode, only : ut=>unicode_type implicit none character(len=*),parameter :: g='(*(g0,1x))' type(ut) :: proverb type(ut) :: delims type(ut),allocatable :: array(:) integer :: first integer :: last integer :: pos integer :: i ! delims= '=|; ' ! proverb="Más vale pájaro en mano, que ciento volando." call printwords(proverb) ! there really are not spaces between these glyphs array=[ & ut("七転び八起き。"), & ut("転んでもまた立ち上がる。"), & ut("くじけずに前を向いて歩いていこう。")] call printwords(array) ! write(stdout,g)'OOP' array=proverb%split(ut(' ')) write(stdout,'(*(:"[",a,"]"))')(character(array(i)),i=1,size(array)) contains impure elemental subroutine printwords(line) type(ut),intent(in) :: line pos = 0 write(stdout,g)line%character(),len(line) do while (pos < len(line)) first = pos + 1 call split (line, delims, pos) last = pos - 1 print g, line%character(first,last),first,last,pos end do end subroutine printwords end program demo_split Results: > Project is up to date > Más vale pájaro en mano, que ciento volando. 44 > Más 1 3 4 > vale 5 8 9 > pájaro 10 15 16 > en 17 18 19 > mano, 20 24 25 > que 26 28 29 > ciento 30 35 36 > volando. 37 44 45 > 七転び八起き。 7 > 七転び八起き。 1 7 8 > 転んでもまた立ち上がる。 12 > 転んでもまた立ち上がる。 1 12 13 > くじけずに前を向いて歩いていこう。 17 > くじけずに前を向いて歩いていこう。 1 17 18 > OOP > [Más][vale][pájaro][en][mano,][que][ciento][volando.] SEE ALSO • tokenize(3) - parse a string into tokens • index(3) - position of a substring within a string • scan(3) - scan a string for the presence of a set of characters • verify(3) - position of a character in a string of characters that does not appear in a given set of characters. AUTHOR Milan Curcic, "milancurcic@hey.com" John S. Urban -- UTF-8 version LICENSE MIT May 05, 2026 split(3m_unicode) sub(3m_unicode) sub(3m_unicode) NAME SUB(3f) - [M_unicode:EDITING] Return substring (LICENSE:MIT) SYNOPSIS impure elemental function sub(str,left,right,step) result(section) type(unicode_type) :: str integer,intent(in),optional :: left integer,intent(in),optional :: right integer,intent(in),optional :: step type(unicode_type) :: section DESCRIPTION sub(3f) returns a substring from one column to another. OPTIONS str the input string to return a section of left column number of str starting section of str to return. Defaults to 1 when STEP is positive, or right end of STR when STEP is negative. right column number of str ending section of str to return. Defaults to right end of STR when STEP is positive, or 1 when STEP is negative. step step to take from left column to right column. Defaults to 1. RETURNS section The specified subsection of the input string EXAMPLES Sample Program: program demo_sub use M_unicode, only : sub, assignment(=) use M_unicode, only : len use M_unicode, only : ut=> unicode_type implicit none type(ut) :: string type(ut) :: piece ! string='abcdefghij' ! piece=sub(string,3,5) call printme('selected range:') piece=sub(string,6) call printme('from character to end:') piece=sub(string,5,5) call printme('single character:') piece=sub(string,step=-1) call printme('reverse string:') contains subroutine printme(label) character(len=*),intent(in) :: label write(*,'(a,"[",g0,"]",/)') label, piece%character() end subroutine printme end program demo_sub Results: > selected range:[cde] > > from character to end:[fghij] > > single character:[e] > > reverse string:[jihgfedcba] > SEE ALSO adjustl(3f), adjustr(3f), repeat(3f), trim(3f), len_trim(3f), len(3f) AUTHOR John S. Urban LICENSE MIT May 05, 2026 sub(3m_unicode) tokenize(3m_unicode) tokenize(3m_unicode) NAME TOKENIZE(3f) - [M_unicode:PARSE] Parse a string into tokens. (LICENSE:MIT) SYNOPSIS TOKEN form (returns array of strings) subroutine tokenize(string, set, tokens [, separator]) type(unicode_type),intent(in) :: string type(unicode_type),intent(in) :: set type(unicode_type),allocatable,intent(out) :: tokens(:) type(unicode_type),allocatable,intent(out),optional :: separator(:) ARRAY BOUNDS form (returns arrays defining token positions) subroutine tokenize (string, set, first, last) type(unicode_type),intent(in) :: string type(unicode_type),intent(in) :: set integer,allocatable,intent(out) :: first(:) integer,allocatable,intent(out) :: last(:) CHARACTERISTICS • STRING ‐ a scalar of type string. It is an INTENT(IN) argument. • SET ‐ a scalar of type string with the same kind type parameter as STRING. It is an INTENT(IN) argument. • SEPARATOR ‐ (optional) shall be of type string. It is an INTENT(OUT)argument. It shall not be a coarray or a coindexed object. • TOKENS ‐ of type string. It is an INTENT(OUT) argument. It shall not be a coarray or a coindexed object. • FIRST,LAST ‐ an allocatable array of type integer and rank one. It is an INTENT(OUT) argument. It shall not be a coarray or a coindexed object. DESCRIPTION TOKENIZE(3) parses a string into tokens. There are two forms of the subroutine TOKENIZE(3). • The token form returns an array with one token per element, all of the same length as the longest token. • The array bounds form returns two integer arrays. One contains the beginning position of the tokens and the other the end positions. Since the token form pads all the tokens to the same length the original number of trailing spaces of each token accept for the longest is lost. The array bounds form retains information regarding the exact token length even when padded by spaces. OPTIONS • STRING : The string to parse into tokens. • SET : Each character in SET is a token delimiter. A sequence of zero or more characters in STRING delimited by any token delimiter, or the beginning or end of STRING, comprise a token. Thus, two consecutive token delimiters in STRING, or a token delimiter in the first or last character of STRING, indicate a token with zero length. • TOKENS : It shall be an allocatable array of rank one with deferred length. It is allocated with the lower bound equal to one and the upper bound equal to the number of tokens in STRING, and with character length equal to the length of the longest token. The tokens in STRING are assigned in the order found, as if by intrinsic assignment, to the elements of TOKENS, in array element order. • FIRST : shall be an allocatable array of type integer and rank one. It is an INTENT(OUT) argument. It shall not be a coarray or a coindexed object. It is allocated with the lower bound equal to one and the upper bound equal to the number of tokens in STRING. Each element is assigned, in array element order, the starting position of each token in STRING, in the order found. If a token has zero length, the starting position is equal to one if the token is at the beginning of STRING, and one greater than the position of the preceding delimiter otherwise. • LAST : It is allocated with the lower bound equal to one and the upper bound equal to the number of tokens in STRING. Each element is assigned, in array element order, the ending position of each token in STRING, in the order found. If a token has zero length, the ending position is one less than the starting position. EXAMPLES Sample of uses program demo_tokenize use M_unicode, only : tokenize, ut=>unicode_type,ch=>character use M_unicode, only : assignment(=),operator(/=) implicit none ! ! some useful formats character(len=*),parameter :: & & brackets='(*("[",g0,"]":,","))' ,& & a_commas='(a,*(g0:,","))' ,& & gen='(*(g0))' ! ! Execution of TOKEN form (return array of tokens) ! block type(ut) :: string type(ut),allocatable :: tokens(:) integer :: i string = ' first,second ,third ' call tokenize(string, set=';,', tokens=tokens ) write(*,brackets)ch(tokens) string = ' first , second ,third ' call tokenize(string, set=' ,', tokens=tokens ) write(*,brackets)(tokens(i)%character(),i=1,size(tokens)) ! remove blank tokens tokens=pack(tokens, tokens /= '' ) write(*,brackets)ch(tokens) ! endblock ! ! Execution of BOUNDS form (return position of tokens) ! block type(ut) :: string character(len=*),parameter :: set = " ," integer,allocatable :: first(:), last(:) write(*,gen)repeat('1234567890',6) string = 'first,second,,fourth' write(*,gen)ch(string) call tokenize (string, set, first, last) write(*,a_commas)'FIRST=',first write(*,a_commas)'LAST=',last write(*,a_commas)'HAS LENGTH=',last-first.gt.0 endblock ! end program demo_tokenize Results: > [ first ],[second ],[third ] > [],[first],[],[],[second],[],[third],[],[],[],[],[] > [first ],[second],[third ] > 123456789012345678901234567890123456789012345678901234567890 > first,second,,fourth > FIRST=1,7,14,15 > LAST=5,12,13,20 > HAS LENGTH=T,T,F,T SEE ALSO • SPLIT(3) ‐ return tokens from a string, one at a time • INDEX(3) ‐ Position of a substring within a string • SCAN(3) ‐ Scan a string for the presence of a set of characters • VERIFY(3) ‐ Position of a character in a string of characters that does not appear in a given set of characters. AUTHOR Milan Curcic, "milancurcic@hey.com" John S. Urban -- UTF-8 version LICENSE MIT May 05, 2026 tokenize(3m_unicode) transliterate(3m_unicode) transliterate(3m_unicode) NAME transliterate(3f) - [M_unicode:EDITING] replace characters from old set with new set (LICENSE:MIT) SYNOPSIS impure elemental & & function transliterate(instr,old_set,new_set) result(outstr) type(unicode_type),intent(in) :: instr type(unicode_type),intent(in) :: old_set type(unicode_type),intent(in) :: new_set type(unicode_type) :: outstr CHARACTERISTICS Although a conversion might occur on each call, the input values may be CHARACTER as well as TYPE(UNICODE_TYPE). DESCRIPTION Translate or delete characters from an input string. OPTIONS instr input string to change old_set list of glyphs to change in INSTR if found Each glyph in the input string that matches a glyph in the old set is replaced. new_set list of glyphs to replace glyphs in OLD_SET with. If NEW_SET is the empty set glyphs in INSTR that match any in OLD_SET are deleted. If NEW_SET is shorter than OLD_SET the last glyph in NEW_SET is used to replace the remaining glyphs in NEW_SET. RETURNS outstr INSTR with substitutions applied EXAMPLES Sample Program: program demo_transliterate use M_unicode, only : transliterate,ut=>unicode_type use M_unicode, only : write(formatted),ch=>character use M_unicode, only : assignment(=) implicit none character(len=*),parameter :: u='(DT)' type(ut) :: STRING, UPPER, LOWER type(ut) :: MIDDLE_DOT STRING='aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ' LOWER='abcdefghijklmnopqrstuvwxyz' UPPER='ABCDEFGHIJKLMNOPQRSTUVWXYZ' call callit() print u print u,ut('Greek') ! ! | Α α | Β β | Γ γ | Δ δ | Ε ε | Ζ ζ | ! | Η η | Θ θ | Ι ι | Κ κ | Λ λ | Μ μ | ! | Ν ν | Ξ ξ | Ο ο | Π π | Ρ ρ | Σ σ ς | ! | Τ τ | Υ υ | Φ φ | Χ χ | Ψ ψ | Ω ω | ! STRING='ΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσςΤτΥυΦφΧχΨψΩω' ! ignoring ς for simplicity UPPER='ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ' LOWER='αβγδεζηθικλμνξοπρστυφχψω' call callit() ! OOP print u print u,ut('OOP!') print u,STRING%TRANSLITERATE(UPPER,'_') ! U+00B7 Middle Dot Unicode Character print u,STRING%TRANSLITERATE(LOWER,'·') ! ASCII bytes print u,STRING%TRANSLITERATE(LOWER,ut('·')) ! cast MIDDLE_DOT=int(z'00B7') print u,STRING%TRANSLITERATE(LOWER,MIDDLE_DOT) ! hexadecimal contains subroutine callit() print u, STRING ! convert -7 string to uppercase: print u, TRANSLITERATE(STRING , LOWER, UPPER ) ! change all miniscule letters to a colon (":"): print u, TRANSLITERATE(STRING, LOWER, ':') ! delete all miniscule letters print u, TRANSLITERATE(STRING, LOWER, '') end subroutine callit end program demo_transliterate Results: > aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ > AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ > :A:B:C:D:E:F:G:H:I:J:K:L:M:N:O:P:Q:R:S:T:U:V:W:X:Y:Z > ABCDEFGHIJKLMNOPQRSTUVWXYZ > > Greek > ΑαΒβΓγΔδΕεΖζΗηΘθΙιΚκΛλΜμΝνΞξΟοΠπΡρΣσςΤτΥυΦφΧχΨψΩω > ΑΑΒΒΓΓΔΔΕΕΖΖΗΗΘΘΙΙΚΚΛΛΜΜΝΝΞΞΟΟΠΠΡΡΣΣςΤΤΥΥΦΦΧΧΨΨΩΩ > Α:Β:Γ:Δ:Ε:Ζ:Η:Θ:Ι:Κ:Λ:Μ:Ν:Ξ:Ο:Π:Ρ:Σ:ςΤ:Υ:Φ:Χ:Ψ:Ω: > ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣςΤΥΦΧΨΩ > > OOP! > _α_β_γ_δ_ε_ζ_η_θ_ι_κ_λ_μ_ν_ξ_ο_π_ρ_σς_τ_υ_φ_χ_ψ_ω > Α·Β·Γ·Δ·Ε·Ζ·Η·Θ·Ι·Κ·Λ·Μ·Ν·Ξ·Ο·Π·Ρ·Σ·ςΤ·Υ·Φ·Χ·Ψ·Ω· > Α·Β·Γ·Δ·Ε·Ζ·Η·Θ·Ι·Κ·Λ·Μ·Ν·Ξ·Ο·Π·Ρ·Σ·ςΤ·Υ·Φ·Χ·Ψ·Ω· > Α·Β·Γ·Δ·Ε·Ζ·Η·Θ·Ι·Κ·Λ·Μ·Ν·Ξ·Ο·Π·Ρ·Σ·ςΤ·Υ·Φ·Χ·Ψ·Ω· AUTHOR John S. Urban LICENSE MIT May 05, 2026 transliterate(3m_unicode) trim(3m_unicode) trim(3m_unicode) NAME TRIM(3f) - [M_unicode:WHITESPACE] remove trailing blank characters from a string (LICENSE:MIT) SYNOPSIS result = trim(string) type(unicode_type) function trim(string) type(unicode_type),intent(in) :: string CHARACTERISTICS • the result is a string. DESCRIPTION trim(3) removes trailing blank characters from a string. OPTIONS • string : a string to trim RESULT the result is the same as string except trailing blanks are removed. if string is composed entirely of blanks or has zero length, the result has zero length. EXAMPLES sample program: program demo_trim use M_unicode, only : ut=>unicode_type, assignment(=) use M_unicode, only : trim, len use M_unicode, only : write(formatted) implicit none type(ut) :: str type(ut), allocatable :: strs(:) character(len=*),parameter :: brackets='( *("[",DT,"]":,1x) )' integer :: i ! str=' trailing ' print brackets, str,trim(str) ! trims it ! str=' leading' print brackets, str,trim(str) ! no effect ! str=' ' print brackets, str,trim(str) ! becomes zero length print *, len(str), len(trim(' ')) ! strs=[ut("Z "),ut(" a b c"),ut("ABC "),ut("")] ! write(*,*)'untrimmed:' print brackets, (strs(i), i=1,size(strs)) print brackets, strs ! write(*,*)'trimmed:' ! everything prints trimmed print brackets, (trim(strs(i)), i=1,size(strs)) print brackets, trim(strs) ! end program demo_trim results: > [ trailing ] [ trailing] > [ leading] [ leading] > [ ] [] > 12 0 > untrimmed: > [Z ] [ a b c] [ABC ] [] > [Z ] [ a b c] [ABC ] [] > trimmed: > [Z] [ a b c] [ABC] [] > [Z] [ a b c] [ABC] [] SEE ALSO Functions that perform operations on character strings, return lengths of arguments, and search for certain arguments: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • nonelemental: len_trim(3), len(3), repeat(3), trim(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 trim(3m_unicode) upper(3m_unicode) upper(3m_unicode) NAME UPPER(3f) - [M_unicode:CASE] changes a string to uppercase (LICENSE:MIT) SYNOPSIS pure elemental function upper(str) result (string) type(unicode_type),intent(in) :: str type(unicode_type) :: string DESCRIPTION upper(string) returns a copy of the input string with all characters converted to uppercase, assuming Unicode character sets are being used. OPTIONS str string to convert to uppercase RETURNS upper copy of the input string with all characters converted to uppercase. TRIVIA The terms "uppercase" and "lowercase" date back to the early days of the mechanical printing press. Individual metal alloy casts of each needed letter, or punctuation symbol, were meticulously added to a press block, by hand, before rolling out copies of a page. These metal casts were stored and organized in wooden cases. The more often needed miniscule letters were placed closer to hand, in the lower cases of the work bench. The less often needed, capitalized, majuscule letters, ended up in the harder to reach upper cases. EXAMPLES Sample program: program demo_upper use iso_fortran_env, only : stdout => output_unit use M_unicode, only : upper, unicode_type, assignment(=) use M_unicode, only : ut => unicode_type, operator(==) implicit none character(len=*),parameter :: g='(*(g0))' type(unicode_type) :: pangram type(unicode_type) :: diacritics type(unicode_type) :: expected ! ! a sentence containing every letter of the English alphabet ! often used to test telegraphs since the advent of the 19th century ! and as an exercise repetitively generated in typing classes pangram = "The quick brown fox jumps over the lazy dog." expected = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG." call test(pangram,expected) ! ! Slovak pangram pangram = 'Vypätá dcéra grófa Maxwella s IQ nižším ako & &kôň núti čeľaď hrýzť hŕbu jabĺk.' expected = 'VYPÄTÁ DCÉRA GRÓFA MAXWELLA S IQ NIŽŠÍM AKO & &KÔŇ NÚTI ČEĽAĎ HRÝZŤ HŔBU JABĹK.' call test(pangram,expected) ! ! contains each special Czech letter with diacritics exactly once print g,'("A horse that was too yellow-ish moaned devilish odes")' diacritics = 'Příliš žluťoučký kůň úpěl ďábelské ódy.' expected = 'PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY.' call test(diacritics,expected) contains subroutine test(in,expected) type(unicode_type),intent(in) :: in type(unicode_type),intent(in) :: expected type(unicode_type) :: uppercase character(len=*),parameter :: nl=new_line('A') write(stdout,g)in%character() uppercase=upper(in) write(stdout,g)uppercase%character() write(stdout,g)merge('PASSED','FAILED',uppercase == expected ),nl end subroutine test end program demo_upper Expected output > The quick brown fox jumps over the lazy dog. > THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. > PASSED > > Vypätá dcéra grófa Maxwella s IQ nižším ako kôň núti ... > čeľaď hrýzť hŕbu jabĺk. > VYPÄTÁ DCÉRA GRÓFA MAXWELLA S IQ NIŽŠÍM AKO KÔŇ NÚTI ... > ČEĽAĎ HRÝZŤ HŔBU JABĹK. > PASSED > > ("A horse that was too yellow-ish moaned devilish odes") > Příliš žluťoučký kůň úpěl ďábelské ódy. > PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY. > PASSED AUTHOR John S. Urban LICENSE MIT May 05, 2026 upper(3m_unicode) utf8_to_codepoints(3m_unicode) utf8_to_codepoints(3m_unicode) NAME UTF8_TO_CODEPOINTS(3f) - [M_unicode:CONVERSION] Convert UTF-8-encoded data to Unicode codepoints (LICENSE:MIT) SYNOPSIS pure subroutine utf8_to_codepoints(utf8,codepoints,nerr) character(len=1),intent(in) :: utf8(:) ! or character(len=*),intent(in) :: utf8 ! integer,allocatable,intent(out) :: codepoints(:) integer,intent(out) :: nerr CHARACTERISTICS • UTF8 is a scalar CHARACTER variable or array of single-byte CHARACTER values • the returned values in CODEPOINTS are of default INTEGER kind • the error flag NERR is default integer kind DESCRIPTION UTF8_TO_CODEPOINTS(3f) takes either a scalar CHARACTER variable or an array of CHARACTER(LEN=1) bytes which are treated as a stream of bytes representing UTF-8-encoded data and converted to an INTEGER array containing Unicode codepoint values for each glyph. OPTIONS • UTF8 : Scalar CHARACTER string or single-character array of CHARACTER variables assumed to represent a stream of bytes containing data encoded at UTF-8 text. • CODEPOINTS : An INTEGER array of Unicode codepoint values representing the glyphs found in STRING • NERR : Zero if no error occurred. If not zero the stream of bytes could not be completely converted to UTF-8 characters. EXAMPLES Sample program program demo_utf8_to_codepoints use m_unicode, only : utf8_to_codepoints implicit none character(len=*),parameter :: string ='Noho me ka hau’oli' !(Be happy) character(len=1),allocatable :: bytes(:) character(len=*),parameter :: solid='(*(g0))' character(len=*),parameter :: space='(*(g0,1x))' character(len=*),parameter :: z='(a,*(z0,1x))' integer,allocatable :: codepoints(:) integer :: nerr integer :: i ! BASIC USAGE: SCALAR CHARACTER VARIABLE write(*,solid)'STRING:',string call utf8_to_codepoints(string,codepoints,nerr) write(*,space)'CODEPOINTS:', codepoints write(*,z)'HEXADECIMAL CODEPOINTS:', codepoints ! write(*,space)'How long is this string in glyphs? ' write(*,space)size(codepoints) write(*,space)'How long is this string in bytes? ' write(*,space)len(string) ! ! BASIC USAGE: ARRAY OF BYTES bytes=[(string(i:i),i=1,len(string))] write(*,solid)'STRING:',bytes call utf8_to_codepoints(bytes,codepoints,nerr) write(*,space)'CODEPOINTS:', codepoints write(*,z)'HEXADECIMAL CODEPOINTS:', codepoints ! write(*,space)'How long is this string in glyphs? ' write(*,space)size(codepoints) write(*,space)'How long is this string in bytes? ' write(*,space)size(bytes) ! end program demo_utf8_to_codepoints Results: > STRING:Noho me ka hau’oli > CODEPOINTS: 78 111 104 111 32 109 101 32 107 97 32 104 97 117 ... > 8217 111 108 105 > 48 4E 6F 68 6F 20 6D 65 20 6B 61 20 68 61 75 2019 6F 6C 69 > How long is this string in glyphs? > 18 > How long is this string in bytes? > 20 > STRING:Noho me ka hau’oli > CODEPOINTS: 78 111 104 111 32 109 101 32 107 97 32 104 97 117 ... > 8217 111 108 105 > 48 4E 6F 68 6F 20 6D 65 20 6B 61 20 68 61 75 2019 6F 6C 69 > How long is this string in glyphs? > 18 > How long is this string in bytes? > 20 SEE ALSO functions that perform operations on character strings: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • non-elemental: len_trim(3), repeat(3), trim(3), codepoints_to_utf8(3) AUTHOR • John S. Urban • Francois Jacq - enhancements and optional Latin support from Francois Jacq, 2025-08 LICENSE MIT May 05, 2026 utf8_to_codepoints(3m_unicode) verify(3m_unicode) verify(3m_unicode) NAME VERIFY(3f) - [M_unicode:SEARCH] Position of a character in a string of characters that does not appear in a given set of characters. (LICENSE:MIT) SYNOPSIS result = verify(string, set [,back] [,kind] ) elemental integer function verify(string,set,back,KIND) type(unicode_type),intent(in) :: string type(unicode_type),intent(in) :: set or character(len=*),intent(in) :: set logical,intent(in),optional :: back CHARACTERISTICS • STRING must be of type string • SET must be of type string or character. • BACK shall be of type logical. • A default integer kind is returned. DESCRIPTION VERIFY(3) verifies that all the characters in STRING belong to the set of characters in SET by identifying the position of the first character in the string that is not in the set. This makes it easy to verify strings are all uppercase or lowercase, follow a basic syntax, only contain printable characters, and many of the conditions tested for with the C routines ISALNUM(3c), ISALPHA(3c), ISASCII(3c), ISBLANK(3c), ISCNTRL(3c), ISDIGIT(3c), ISGRAPH(3c), ISLOWER(3c), ISPRINT(3c), ISPUNCT(3c), ISSPACE(3c), ISUPPER(3c), and ISXDIGIT(3c); but for a string as well as an array of strings. OPTIONS • STRING : The string to search in for an unmatched character. • SET : The set of characters that must be matched. • BACK : The direction to look for an unmatched character. The left‐most unmatched character position isreturned unless BACK is present and .false., which causes the position of the right‐most unmatched character to be returned instead of the left‐most unmatched character. RESULT If all characters of STRING are found in SET, the result is zero. If STRING is of zero length a zero (0) is always returned. Otherwise, if an unmatched character is found The position of the first or last (if BACK is .false.) unmatched character in STRING is returned, starting with position one on the left end of the string. EXAMPLES Sample program I: program demo_verify ! general examples use M_unicode, only : assignment(=) use M_unicode, only : ut=>unicode_type, ch=>character use M_unicode, only : write(formatted) use M_unicode, only : operator(==) use M_unicode, only : verify, replace use M_unicode, only : operator(//) implicit none ! some useful character sets character,parameter :: & & int*(*) = "1234567890", & & low*(*) = "abcdefghijklmnopqrstuvwxyz", & & upp*(*) = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", & & punc*(*) = "!""#$%&'()*+,‐./:;<=>?@[\]'_‘{|}˜", & & blank*(*) = " ", & & tab = char(11), & & prnt*(*) = int//low//upp//blank//punc ! type(ut) :: stru integer :: i print *, "basics:" print *, VERIFY ("ABBA", "A") ! has the value 2. print *, VERIFY ("ABBA", "A", BACK = .TRUE.) ! has the value 3. print *, VERIFY ("ABBA", "AB") ! has the value 0. ! print *,"find first non‐uppercase letter" ! will produce the location of "d", because there is no match in UPP write(*,*) "something unmatched",verify(ut("ABCdEFG"), upp) ! print *,"if everything is matched return zero" ! will produce 0 as all letters have a match write(*,*) & & "everything matched",verify(ut("ffoorrttrraann"), "nartrof") ! print *,"easily categorize strings as uppercase, lowercase, ..." ! C-like functionality but does entire strings not just characters write(*,*)"isdigit 123?",verify(ut("123"), int) == 0 write(*,*)"islower abc?",verify(ut("abc"), low) == 0 write(*,*)"isalpha aBc?",verify(ut("aBc"), low//upp) == 0 write(*,*)"isblank aBc dEf?",verify(ut("aBc dEf"), blank//tab ) /= 0 ! check if all printable characters stru="aB;cde,fgHI!Jklmno PQRSTU vwxyz" write(*,*)"isprint?",verify(stru,prnt) == 0 ! ! this now has a nonprintable tab character in it stru=replace(stru,10,10,ut(char(11))) write(*,*)"isprint?",verify(stru,prnt) == 0 ! print *,"VERIFY(3) is very powerful using expressions as masks" ! verify(3) is often used in a logical expression stru=" This is NOT all UPPERCASE " write(*,*)"all uppercase/spaces?",verify(stru, blank//upp) == 0 stru=" This IS all uppercase " write(*,*) "stru=["//stru//"]" write(*,*)"all uppercase/spaces?",verify(stru, blank//upp) == 0 ! ! set and show complex stru to be tested stru=" Check this out. Let me know " ! show the stru being examined write(*,*) "stru=["//stru//"]" write(*,*) " "//repeat(int,4) ! number line ! ! function returns a position just not a logical like C print *, "returning a position not just a logical is useful" ! which can be very useful for parsing strings write(*,*)"first non‐blank character",verify(stru, blank) write(*,*)"last non‐blank character",verify(stru, blank,back=.true.) write(*,*)"first non‐letter non‐blank",verify(stru,low//upp//blank) ! !VERIFY(3) is elemental (can check an array of strings in one call) print *, "elemental" ! are strings all letters (or blanks)? write(*,*) "array of strings",verify( & ! strings must all be same length, so force to length 10 & [character(len=10) :: "YES","ok","000","good one","Nope!"], & & low//upp//blank) == 0 ! ! rarer, but the set can be an array, not just the strings to test ! you could do ISPRINT() this (harder) way :> write(*,*)"isprint?", & & .not.all(verify(ut("aBc"), [(char(i),i=32,126)])==1) ! instead of this way write(*,*)"isprint?",verify(ut("aBc"),prnt) == 0 ! end program demo_verify Results: > basics: > 2 > 3 > 0 > find first non‐uppercase letter > something unmatched 4 > if everything is matched return zero > everything matched 0 > easily categorize strings as uppercase, lowercase, ... > isdigit 123? T > islower abc? T > isalpha aBc? T > isblank aBc dEf? T > isprint? T > isprint? F > VERIFY(3) is very powerful using expressions as masks > all uppercase/spaces? F > string=[ This IS all uppercase ] > all uppercase/spaces? F > string=[ Check this out. Let me know ] > 1234567890123456789012345678901234567890 > returning a position not just a logical is useful > first non‐blank character 3 > last non‐blank character 29 > first non‐letter non‐blank 17 > elemental > array of strings T T F T F > isprint? T > isprint? T Sample program II: Determine if strings are valid integer representations program fortran_ints use M_unicode, only : ut=>unicode_type,assignment(=) use M_unicode, only : adjustr, verify, trim, len use M_unicode, only : write(formatted) use M_unicode, only : operator(.cat.) use M_unicode, only : operator(==) implicit none integer :: i character(len=*),parameter :: asciiints(*)=[character(len=10) :: & "+1 ", & "3044848 ", & "30.40 ", & "September ", & "1 2 3", & " -3000 ", & " "] type(ut),allocatable :: ints(:) if(allocated(ints))deallocate(ints) allocate(ints(size(asciiints))) ! gfortran bug ints=asciiints ints=trim(ints) ! show if strings pass or fail the test done by isint(3) write(*,"('is integer?')") do i=1,size(ints) write(*,'("|",DT,T14,"|",l1,"|")') ints(i), isint(ints(i)) enddo ! elemental write(*,"(*(g0,1x))") isint(ints) contains impure elemental function isint(line) result (lout) use M_unicode, only : adjustl, verify, trim ! ! determine if string is a valid integer representation ! ignoring trailing spaces and leading spaces ! character(len=*),parameter :: digits="0123456789" type(ut),intent(in) :: line type(ut) :: name logical :: lout lout=.false. ! make sure at least two characters long to simplify tests name=adjustl(line).cat.' ' ! blank string if( name == '' )return ! allow one leading sign if( verify(name%sub(1,1),ut('+‐-')) == 0 ) name=name%sub(2,len(name)) ! was just a sign if( name == '' )return lout=verify(trim(name), digits) == 0 end function isint end program fortran_ints Results: > is integer? > |+1 |T| > |3044848 |T| > |30.40 |F| > |September |F| > |1 2 3 |F| > | ‐3000 |T| > | |F| > T T F F F T F Sample program III: Determine if strings represent valid Fortran symbol names program fortran_symbol_name use M_unicode, only : ut=>unicode_type, trim, verify, len use M_unicode, only : ch=>character use M_unicode, only : write(formatted) implicit none integer :: i type(ut),allocatable :: symbols(:) symbols=[ & ut('A_'), ut('10'), ut('a10'), ut('September'), ut('A B'), & ut('_A'), ut(' ')] do i=1,size(symbols) write(*,'(1x,DT,T11,"|",l2)')symbols(i),fortran_name(symbols(i)) enddo contains impure elemental function fortran_name(line) result (lout) ! ! determine if a string is a valid Fortran name ! ignoring trailing spaces (but not leading spaces) ! character(len=*),parameter :: int="0123456789" character(len=*),parameter :: lower="abcdefghijklmnopqrstuvwxyz" character(len=*),parameter :: upper="ABCDEFGHIJKLMNOPQRSTUVWXYZ" character(len=*),parameter :: allowed=upper//lower//int//"_" type(ut),intent(in) :: line type(ut) :: name logical :: lout name=trim(line) if(len(name).ne.0)then ! first character is alphameric lout = verify(name%sub(1,1), lower//upper) == 0 & ! other characters are allowed in a symbol name & .and. verify(name,allowed) == 0 & ! allowable length & .and. len(name) <= 63 else lout = .false. endif end function fortran_name end program fortran_symbol_name Results: > A_ | T > 10 | F > a10 | T > September| T > A B | F > _A | F > | F Sample program IV: check if string is of form NN‐HHHHH program form ! ! check if string is of form NN‐HHHHH ! use iso_fortran_env, only : stdout => output_unit use M_unicode, only : verify, unicode_type, assignment(=) use M_unicode, only : ut=>unicode_type implicit none character(len=*),parameter :: g='(*(g0,1x))' ! character(len=*),parameter :: int='1234567890' character(len=*),parameter :: hex='abcdefABCDEF0123456789' logical :: lout type(unicode_type) :: chars type(unicode_type) :: str ! chars='32‐af43d' lout=.true. ! ! are the first two characters integer characters? str = chars%character(1,2) lout = (verify( str, ut(int) ) == 0) .and.lout ! ! is the third character a dash? str = chars%character(3,3) lout = (verify( str, ut('‐-') ) == 0) .and.lout ! ! is remaining string a valid representation of a hex value? str = chars%character(4,8) lout = (verify( str, ut(hex) ) == 0) .and.lout ! if(lout)then write(stdout,g)trim(chars%character()),' passed' else write(stdout,g)trim(chars%character()),' failed' endif end program form Results: > 32‐af43d passed Sample program V: exploring uses of elemental functionality and dusty corners program more_verify use M_unicode, only : ut=>unicode_type, verify use M_unicode, only : assignment(=) use M_unicode, only : ch=>character implicit none character(len=*),parameter :: & & low="abcdefghijklmnopqrstuvwxyz", & & upp="ABCDEFGHIJKLMNOPQRSTUVWXYZ", & & blank=" " ! note character variables in an array have to be of the same length type(ut),allocatable :: strings(:) type(ut),allocatable :: sets(:) strings=[ut("Go"),ut("right"),ut("home!")] sets=[ut("do"),ut("re"),ut("me")] ! elemental ‐‐ you can use arrays for both strings and for sets ! check each string from right to left for non‐letter/non‐blank write(*,*)"last non‐letter",verify(strings,upp//low//blank,back=.true.) ! even BACK can be an array ! find last non‐uppercase character in "Go" ! and first non‐lowercase in "right" write(*,*) verify(strings(1:2),[upp,low],back=[.true.,.false.]) ! using a null string for a set is not well defined. Avoid it write(*,*) "null",verify("for tran ", "", .true.) ! 8,length of string? ! probably what you expected write(*,*) "blank",verify("for tran ", " ", .true.) ! 7,found ’n’ ! first character in "Go " not in "do", ! and first letter in "right " not in "ri" ! and first letter in "home! " not in "me" write(*,*) verify(strings,sets) end program more_verify Results: > last non‐letter 0 0 5 > 2 0 > null 9 > blank 8 > 1 2 1 SEE ALSO Functions that perform operations on character strings, return lengths of arguments, and search for certain arguments: • ELEMENTAL: ADJUSTL(3), ADJUSTR(3), INDEX(3), SCAN(3), • NONELEMENTAL: LEN_TRIM(3), LEN(3), REPEAT(3), TRIM(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 verify(3m_unicode) glob(3m_unicode) glob(3m_unicode) NAME glob(3f) - [M_unicode:COMPARE] compare given string for match to a pattern which may contain globbing wildcard characters (LICENSE:MIT) SYNOPSIS logical function glob(string, pattern ) result (uline) type(unicode_type),intent(in) :: string ! or character(len=*),intent(in) :: string type(unicode_type),intent(in) :: pattern ! or character(len=*),intent(in) :: pattern logical :: uline DESCRIPTION glob(3f) compares an (entire) STRING for a match to a PATTERN which may contain basic wildcard "globbing" characters. • "*" matches any string. • "?" matches any single character. • a NULL may not appear in the input strings • trailing whitespace is significant In this version to get a match the entire string must be described by PATTERN. Trailing whitespace is significant, so trim the input string if it is desired to ignore trailing whitespace. A NULL character is added to the input strings internally to avoid early matches. Without this, patterns like "b*ba" fail on a string like "babababa" because the first match found is not at the end of the string so 'baba' does not match 'babababa'. So the algorithm is said to find an early match. One method that allows skipping over the early matches is to insert an extra character at the end of the string and pattern that does not occur in the pattern. Typically a NULL is used (char(0)), as it is here. So searching for b*ba\0 in babababa\0 matches the entire string. OPTIONS string the input string to be tested for a match to the pattern. pattern the globbing pattern to search for. The following simple globbing rules apply: • "?" matching any one character • "*" matching zero or more characters. Do NOT use adjacent asterisks. • The input strings must not contain a NULL character • spaces are significant and must be matched. • There is no escape character, so matching strings with a literal question mark and asterisk is problematic. EXAMPLES Example program program demo_glob use M_unicode, only : glob, trim, unicode_type, len use M_unicode, only : escape use M_unicode, only : assignment(=) implicit none integer :: i type(unicode_type),allocatable :: ufiles(:) type(unicode_type),allocatable :: matched(:) character(len=*),parameter :: & filenames(*)= [character(len=256) :: & & 'My_favorite_file.F90', & ! English & '我最喜欢的文档.c', & ! Mandarin_Chinese & 'मरी_पसदीदा_फाइल.f90', & ! Hindu & 'Mi_archivo_favorito.c', & ! Spanish & 'ملفي_المفضل.h', & ! Modern_Standard_Arabic & 'Mon_fichier_préféré.f90', & ! French & 'আমার_পরিয_ফাইল', & ! Bengali & 'Meu_arquivo_favorito', & ! Portuguese & 'Мой_любимый_файл', & ! Russian & 'میری_پسندیدہ_فائل.pdf', & ! Urdu & 'src/M_modules.F90', & & 'src/subset.inc', & & 'test/check.f90 ', & & 'app/main.f90 '] character(len=*),parameter :: & encoded(*)= [character(len=256) :: & & 'My_favorite_file.F90', & ! English & '\u6211\u6700\u559C\u6B22\u7684\u6587\u6863.c', & ! Mandarin_Chinese & '\u092E\u0947\u0930\u0940_& &\u092A\u0938\u0902\u0926\u0940\u0926\u093E_& &\u092B\u093C\u093E\u0907\u0932.f90', & ! Hindu & 'Mi_archivo_favorito.c', & ! Spanish & '\u0645\u0644\u0641\u064A_& &\u0627\u0644\u0645\u0641\u0636\u0644.h ', & ! Modern_Standard_Arabic & 'Mon_fichier_pr\xE9f\xE9r\xE9.f90', & ! French & '\u0986\u09AE\u09BE\u09B0_\u09AA\u09CD\u09B0\u09BF\u09AF\u09BC_& &\u09AB\u09BE\u0987\u09B2', & ! Bengali & 'Meu_arquivo_favorito', & ! Portuguese & '\u041C\u043E\u0439_\u043B\u044E\u0431\u0438\u043C\u044B\u0439_& &\u0444\u0430\u0439\u043B', & ! Russian & '\u0645\u06CC\u0631\u06CC_& &\u067E\u0633\u0646\u062F\u06CC\u062F\u06C1_& &\u0641\u0627\u0626\u0644.pdf', & ! Urdu & 'src/M_modules.F90', & & 'src/subset.inc', & & 'test/check.f90 ', & & 'app/main.f90 '] character(len=*),parameter :: & g='(*(g0))', g1='(*(g0,1x))', comma='(*(g0:,", ",/))' ! some basic usage write(*,g)merge('PASSED','FAILED',glob("mississipPI", "*issip*PI")) write(*,g)merge('PASSED','FAILED',glob("bLah", "bL?h")) write(*,g)merge('PASSED','FAILED',glob("bLaH", "?LaH")) ! create a list of trimmed filenames ufiles=unicode_type(filenames) ufiles=trim(ufiles) write(*,g)'FILENAMES:' call show_filenames(ufiles) ! create a list of trimmed filenames from encoded names ufiles=escape(encoded) ufiles=trim(ufiles) write(*,g)'ENCODED FILENAMES:' call show_filenames(ufiles) ! get filenames ending in ".f90" matched=pack(ufiles,glob(ufiles,'*.f90')) write(*,g)'MATCHED *.f90:' call show_filenames(matched) ! get filenames ending in ".c" matched=pack(ufiles,glob(ufiles,'*.c')) write(*,g)'MATCHED *.c:' call show_filenames(matched) contains subroutine show_filenames(names) type(unicode_type),allocatable :: names(:) write(*,g1)':SIZE:',size(names),':LEN:',len(names) write(*,comma)(names(i)%character(),i=1,size(names)) end subroutine show_filenames end program demo_glob Results: > PASSED > PASSED > PASSED > FILENAMES: > :SIZE: 14 :LEN: 20 9 22 21 13 23 16 20 16 21 17 14 14 12 > My_favorite_file.F90, > 我最喜欢的文档.c, > मरी_पसदीदा_फाइल.f90, > Mi_archivo_favorito.c, > ملفي_المفضل.h, > Mon_fichier_préféré.f90, > আমার_পরিয_ফাইল, > Meu_arquivo_favorito, > Мой_любимый_файл, > میری_پسندیدہ_فائل.pdf, > src/M_modules.F90, > src/subset.inc, > test/check.f90, > app/main.f90 > ENCODED FILENAMES: > :SIZE: 14 :LEN: 20 9 22 21 13 23 16 20 16 21 17 14 14 12 > My_favorite_file.F90, > 我最喜欢的文档.c, > मरी_पसदीदा_फाइल.f90, > Mi_archivo_favorito.c, > ملفي_المفضل.h, > Mon_fichier_préféré.f90, > আমার_পরিয_ফাইল, > Meu_arquivo_favorito, > Мой_любимый_файл, > میری_پسندیدہ_فائل.pdf, > src/M_modules.F90, > src/subset.inc, > test/check.f90, > app/main.f90 > MATCHED *.f90: > :SIZE: 4 :LEN: 22 23 14 12 > मरी_पसदीदा_फाइल.f90, > Mon_fichier_préféré.f90, > test/check.f90, > app/main.f90 > MATCHED *.c: > :SIZE: 2 :LEN: 9 21 > 我最喜欢的文档.c, > Mi_archivo_favorito.c AUTHOR John S. Urban REFERENCES The article "Matching Wildcards: An Empirical Way to Tame an Algorithm" in Dr Dobb's Journal, By Kirk J. Krauss, October 07, 2014 LICENSE MIT May 05, 2026 glob(3m_unicode) ichar(3m_unicode) ichar(3m_unicode) NAME ICHAR(3f) - [M_unicode:CONVERSION] character-to-integer code conversion function (LICENSE:MIT) SYNOPSIS result = ichar(c) elemental integer function ichar(c,kind) type(unicode_type),intent(in) :: c CHARACTERISTICS • c is a scalar character • the return value is of default integer kind. DESCRIPTION ichar(3) returns the code for the character in the system's native character set. the correspondence between characters and their codes is not necessarily the same across different Fortran implementations. For example, a platform using EBCDIC would return different values than an ASCII platform. See IACHAR(3) for specifically working with the ASCII character set. OPTIONS • C : The input character to determine the decimal code of. RESULT The codepoint in the Unicode character set for the character being queried is returned. The result is the position of C in the Unicode collating sequence, which is generally not the dictionary order in a particular language. It is nonnegative and less than n, where n is the number of characters in the collating sequence. For any characters C and D capable of representation in the processor, C <= D is true if and only if ICHAR(C) <= ICHAR(D) is true and C == D is true if and only if ICHAR(C) == ICHAR(D) is true. EXAMPLES sample program: program demo_ichar use M_unicode, only : assignment(=),ch=>character use M_unicode, only : ut=>unicode_type, write(formatted) use M_unicode, only : ichar, escape, len implicit none type(ut) :: string type(ut),allocatable :: lets(:) integer,allocatable :: ilets(:) integer :: i ! ! create a string containing multibyte characters string=[949, 8021, 961, 951, 954, 945, 33] ! eureka write(*,'(*(DT,1x,"(AKA. eureka!)"))')string ! ! call ichar(3) on each glyph of the string to convert ! the string to an array of integer codepoints ilets=[(ichar(string%sub(i,i)),i=1,len(string))] write(*,'(*(z0,1x))')ilets ! ! note that the %codepoint method is commonly used to ! convert a string to an integer array of codepoints write(*,'(*(z0,1x))')string%codepoint() ! elemental write(*,'("WRITING ISSUES:")') ! ! define an array LETS with escape codes with one glyph per element lets=[ut('\U03B5'),ut('\U1F55'),ut('\U03C1'),ut('\U03B7'), & & ut('\U03BA'),ut('\U03B1'),ut('\U0021')] lets=escape(lets) ! convert escape codes to glyphs ! ! look at issues with converting to CHARACTER for simple printing ! write(*,'("each element is a single glyph ",*(g0,1x))')len(lets) ! ! notice if you convert to an array of intrinsic CHARACTER type the ! strings are all the same length in bytes; but unicode characters ! can take various numbers of bytes write(*,'(*(g0,":"))')'CHARACTER array elements have same length',& & len(ch(lets)) ! this will not appear correctly because all elements are padded to ! the same length in bytes write(*,'(*(a,":"))')ch(lets) ! one element at a time will retain the size of each element write(*,'(*(a,":"))')(ch(lets(i:i)),i=1,size(lets)) ! ! the FIRST LETTER of each element is converted to a codepoint so ! for the special case where each string element is a single glyph ! an elemental approach works write(*,'("ELEMENTAL:",*(z0,1x))')ichar(lets) ! OOPS write(*,'("OOPS:",*(z0,1x))')lets%ichar() end program demo_ichar results: > Project is up to date > εὕρηκα! (AKA. eureka!) > 3B5 1F55 3C1 3B7 3BA 3B1 21 > 3B5 1F55 3C1 3B7 3BA 3B1 21 > WRITING ISSUES: > each element is a single glyph 1 1 1 1 1 1 1 > CHARACTER array elements have same length:3: > ε :ὕ:ρ :η :κ :α :! : > ε:ὕ:ρ:η:κ:α:!: > ELEMENTAL:3B5 1F55 3C1 3B7 3BA 3B1 21 > OOPS:3B5 1F55 3C1 3B7 3BA 3B1 21 SEE ALSO achar(3), char(3), iachar(3) functions that perform operations on character strings, return lengths of arguments, and search for certain arguments: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • nonelemental: len_trim(3), len(3), repeat(3), trim(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 ichar(3m_unicode) index(3m_unicode) index(3m_unicode) NAME INDEX(3f) - [M_unicode:SEARCH] Position of a substring within a string (LICENSE:MIT) SYNOPSIS result = index( string, substring [,back] [,kind] ) elemental integer(kind=KIND) function index(string,substring,back,kind) character(len=*,kind=KIND),intent(in) :: string character(len=*,kind=KIND),intent(in) :: substring logical(kind=**),intent(in),optional :: back integer(kind=**),intent(in),optional :: kind CHARACTERISTICS • STRING is a character variable of any kind • SUBSTRING is a character variable of the same kind as STRING • BACK is a logical variable of any supported kind • KIND is a scalar integer constant expression. DESCRIPTION INDEX(3) returns the position of the start of the leftmost or rightmost occurrence of string SUBSTRING in STRING, counting from one. If SUBSTRING is not present in STRING, zero is returned. OPTIONS • STRING : string to be searched for a match • SUBSTRING : string to attempt to locate in STRING • BACK : If the BACK argument is present and true, the return value is the start of the rightmost occurrence rather than the leftmost. • KIND : if KIND is present, the kind type parameter is that specified by the value of KIND; otherwise the kind type parameter is that of default integer type. RESULT The result is the starting position of the first substring SUBSTRING found in STRING. If the length of SUBSTRING is longer than STRING the result is zero. If the substring is not found the result is zero. If BACK is .true. the greatest starting position is returned (that is, the position of the right‐most match). Otherwise, the smallest position starting a match (ie. the left‐most match) is returned. The position returned is measured from the left with the first character of STRING being position one. Otherwise, if no match is found zero is returned. EXAMPLES Example program program demo_index use M_unicode, only : ut=>unicode_type use M_unicode, only : assignment(=) use M_unicode, only : index implicit none type(ut) :: str character(len=*),parameter :: all='(*(g0))' integer :: ii ! str='Huli i kēia kaula no kēia ʻōlelo' !bug!print all, index(str,'kēia').eq.8 ii=index(str,'kēia'); print all, ii.eq.8 ! ! return value is counted from the left end even if BACK=.TRUE. !bug!print all, index(str,'kēia',back=.true.).eq.22 ii=index(str,'kēia',back=.true.); print all, ii.eq.22 ! ! INDEX is case-sensitive !bug!print all, index(str,'Kēia').eq.0 ii=index(str,'Kēia'); print all, ii.eq.0 !<<<<<<<<<< !ifx bug: ifx (IFX) 2024.1.0 20240308 ! !example/demo_index.f90(17): error #6766: A binary defined OPERATOR !definition is missing or incorrect. [EQ] ! print all, index(str,'k ia',back=.true.).eq.22 !--------------------------------------------------^ !Original works with gfortran and flang_new and this works with ifx ! ii=ndex(str,'k ia',back=.true.) ! print all, ii.eq.22 !>>>>>>>>>> end program demo_index Expected Results: > T > T > T > T > T > T SEE ALSO Functions that perform operations on character strings, return lengths of arguments, and search for certain arguments: • ELEMENTAL: ADJUSTL(3), ADJUSTR(3), INDEX(3), SCAN(3), VERIFY(3) • NONELEMENTAL: LEN_TRIM(3), LEN(3), REPEAT(3), TRIM(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 index(3m_unicode) isascii(3m_unicode) isascii(3m_unicode) NAME isascii(3f) - [M_unicode:QUERY] returns .true. if all the characters of a string are in the set from CHAR(0) to CHAR(127). (LICENSE:MIT) SYNOPSIS function isascii(str) character(len=*),intent(in) :: str or type(ut),intent(in) :: str logical :: isascii DESCRIPTION isascii(3f) returns .true. if all the characters in the string are ASCII-7 characters (ie. in the range char(0) to char(127). OPTIONS str character variable or string to test RETURNS isascii logical value returns true if all the characters in the string represent ASCII-7 characters. EXAMPLES Sample program program demo_isascii use M_unicode, only : ut=>unicode_type, assignment(=) use M_unicode, only : isascii, ch=>character implicit none integer :: i character(len=256) :: ascii8 type(ut) :: uascii8 type(ut) :: ustring character(len=:),allocatable :: astring do i=1,256 ascii8(i:i)=char(i-1) enddo uascii8=[(i,i=0,255)] write(*,*)'CHARACTER: all of ascii8',isascii(ascii8) write(*,*)'CHARACTER: all of ascii7',isascii(ascii8(1:128)) write(*,*)'UNICODE TYPE:all of ascii8',isascii(uascii8) write(*,*)'UNICODE TYPE:all of ascii7',isascii(uascii8%sub(1,128)) ! French pangram translates from the French to ! "Take this old whisky to the blond judge who is smoking." astring='Portez ce vieux whisky au juge blond qui fume.' ustring=astring write(*,*)'CHARACTER: ',isascii(astring),astring write(*,*)'UNICODE_TYPE:',isascii(ustring),ch(ustring) ! (variant with “é”) astring='Portez ce vieux whisky au juge blond qui a fumé.' ustring=astring write(*,*)'CHARACTER ',isascii(ustring),ch(ustring) write(*,*)'UNICODE_TYPE:',isascii(ustring),ch(ustring) end program demo_isascii Results: AUTHOR John S. Urban LICENSE MIT May 05, 2026 isascii(3m_unicode) isblank(3m_unicode) isblank(3m_unicode) NAME isblank(3f) - [M_unicode:COMPARE] returns .true. if character is a Unicode or ASCII-7 blank character (space or horizontal tab) . (LICENSE:MIT) SYNOPSIS elemental function isblank(onechar) type(unicode_type),intent(in) :: string !or character(len=*,intent(in) :: characters logical :: isblank DESCRIPTION isblank(3f) returns .true. if all characters are a blank character (ASCII-7 space or Unicode blank character) or horizontal tab. OPTIONS str variable to test RETURNS isblank logical value returns true if character is a "blank" ( an ASCII space or Unicode blank) or horizontal tab character. EXAMPLES Sample program: program demo_isblank use M_unicode, only : isblank, unicode, ch=>character, unicode_type use M_unicode, only : assignment(=) implicit none integer :: i type(unicode_type) :: string_u character(len=1),parameter :: string_a(*)=[(char(i),i=0,127)] write(*,'(*(g0,1x))')'ISBLANK PASSED TYPE(CHARACTER) : ',isblank(string_a) string_u=unicode%SPACES write(*,'(*(g0,1x))')'ISBLANK PASSED TYPE(UNICODE_TYPE): ',isblank(string_u) write(*,'(*(g0))')'BLANKS: ',ch(string_u) write(*,'(*(g0),1x)')'BLANKS: ',string_u%codepoint() end program demo_isblank Results: ISBLANK: 9 32 AUTHOR John S. Urban LICENSE MIT May 05, 2026 isblank(3m_unicode) isspace(3m_unicode) isspace(3m_unicode) NAME isspace(3f) - [M_unicode:COMPARE] returns .true. if character is a null, space, tab, carriage return, new line, vertical tab, or formfeed (LICENSE:MIT) SYNOPSIS elemental function isspace(onechar) character,intent(in) :: onechar logical :: isspace DESCRIPTION isspace(3f) returns .true. if character is a null, space, tab, carriage return, new line, vertical tab, or formfeed OPTIONS onechar character to test RETURNS isspace returns true if character is ASCII white space EXAMPLES Sample program: program demo_isspace use M_unicode, only : isspace implicit none integer :: i character(len=1),parameter :: string(*)=[(char(i),i=0,127)] write(*,'(20(g0,1x))')'ISSPACE: ', & & iachar(pack( string, isspace(string) )) end program demo_isspace Results: ISSPACE: 0 9 10 11 12 13 32 AUTHOR John S. Urban LICENSE MIT May 05, 2026 isspace(3m_unicode) join(3m_unicode) join(3m_unicode) NAME JOIN(3f) - [M_unicode:EDITING] append CHARACTER variable array into a single CHARACTER variable with specified separator (LICENSE:MIT) SYNOPSIS impure function join(str,sep,clip) result (string) type(unicode_type),intent(in) :: str(:) type(unicode_type),intent(in),optional :: sep logical,intent(in),optional :: clip type(unicode_type),allocatable :: string DESCRIPTION JOIN(3f) appends the elements of a CHARACTER array into a single CHARACTER variable, with elements 1 to N joined from left to right. By default each element is trimmed of trailing spaces and the default separator is a null string. OPTIONS STR array of variables to be joined SEP separator string to place between each variable. defaults to a null string. CLIP option to trim each element of STR of trailing and leading spaces. Defaults to .TRUE. RETURNS STRING CHARACTER variable composed of all of the elements of STR() appended together with the optional separator SEP placed between the elements. EXAMPLES Sample program: program demo_join use M_unicode, only : join, ut=>unicode_type, ch=>character, assignment(=) !use M_unicode, only : write(formatted) implicit none character(len=*),parameter :: w='((g0,/,g0))' !character(len=*),parameter :: v='((g0,/,DT))' character(len=20),allocatable :: proverb(:) type(ut),allocatable :: s(:) type(ut),allocatable :: sep ! proverb=[ character(len=13) :: & & ' United' ,& & ' we' ,& & ' stand,' ,& & ' divided' ,& & ' we fall.' ] ! if(allocated(s))deallocate(s) allocate(s(size(proverb))) ! avoid GNU Fortran (GCC) 16.0.0 bug s=proverb write(*,w) 'SIMPLE JOIN: ', ch( join(s) ) write(*,w) 'JOIN WITH SEPARATOR: ', ch( join(s,sep=ut(' ')) ) write(*,w) 'CUSTOM SEPARATOR: ', ch( join(s,sep=ut('<-->')) ) write(*,w) 'NO TRIMMING: ', ch( join(s,clip=.false.) ) ! sep=ut() write(*,w) 'SIMPLE JOIN: ', ch(sep%join(s) ) sep=' ' write(*,w) 'JOIN WITH SEPARATOR: ', ch(sep%join(s) ) sep='<-->' write(*,w) 'CUSTOM SEPARATOR: ', ch(sep%join(s) ) sep='' write(*,w) 'NO TRIMMING: ', ch(sep%join(s,clip=.false.) ) end program demo_join Results: > SIMPLE JOIN: > Unitedwestand,dividedwe fall. > JOIN WITH SEPARATOR: > United we stand, divided we fall. > CUSTOM SEPARATOR: > United==>we==>stand,==>divided==>we fall. > NO TRIMMING: > United we stand, divided we fall. > SIMPLE JOIN: > Unitedwestand,dividedwe fall. > JOIN WITH SEPARATOR: > United we stand, divided we fall. > CUSTOM SEPARATOR: > United==>we==>stand,==>divided==>we fall. > NO TRIMMING: > United we stand, divided we fall. AUTHOR John S. Urban LICENSE MIT May 05, 2026 join(3m_unicode) codepoints_to_utf8(3m_unicode) codepoints_to_utf8(3m_unicode) NAME CODEPOINTS_TO_UTF8(3f) - [M_unicode:CONVERSION] convert codepoints to CHARACTER (LICENSE:MIT) SYNOPSIS pure subroutine codepoints_to_utf8(codepoints,utf8,nerr) integer,allocatable,intent(in) :: codepoints(:) ! character(len=1),intent(out) :: utf8(:) ! or character(len=*),intent(out) :: utf8 ! integer,intent(out) :: nerr CHARACTERISTICS • UTF8 is a scalar or array CHARACTER variable • CODEPOINTS is of default INTEGER kind • NERR is of default INTEGER kind DESCRIPTION CODEPOINTS_TO_UTF8(3f) takes an integer array of Unicode codepoint values and generates either a scalar CHARACTER variable or an array of bytes (AKA. CHARACTER(LEN=1)) which are assumed to contain a stream of bytes representing UTF-8-encoded data. OPTIONS • CODEPOINTS : An INTEGER array of Unicode codepoint values representing the glyphs to be encoded at UTF-8 data • UTF8 : Scalar or single-character array CHARACTER variables to contain a stream of bytes containing data encoded at UTF-8 text. • NERR : Zero if no error occurred. If not zero the stream of bytes could not be completely converted to UTF-8 characters. EXAMPLES Sample program program demo_codepoints_to_utf8 use m_unicode, only : codepoints_to_utf8 implicit none !'Noho me ka hau’oli' !(Be happy) integer,parameter :: codepoints(*)=[ & & 78,111,104,111,& & 32,109,101, & & 32,107,97, & & 32,104,97,117,8217,111,108,105] character(len=:),allocatable :: string character(len=1),allocatable :: bytes(:) character(len=*),parameter :: solid='(*(g0))' character(len=*),parameter :: space='(*(g0,1x))' character(len=*),parameter :: z='(a,*(z0,1x))' integer :: nerr ! BASIC USAGE: SCALAR CHARACTER VARIABLE write(*,space)'CODEPOINTS:', codepoints write(*,z)'HEXADECIMAL CODEPOINTS:', codepoints call codepoints_to_utf8(codepoints,string,nerr) write(*,solid)'STRING:',string ! write(*,space)'How long is this string in glyphs? ' write(*,space)size(codepoints) write(*,space)'How long is this string in bytes? ' write(*,space)len(string) ! ! BASIC USAGE: ARRAY OF BYTES call codepoints_to_utf8(codepoints,bytes,nerr) write(*,solid)'STRING:',bytes ! write(*,space)'How long is this string in glyphs? ' write(*,space)size(codepoints) write(*,space)'How long is this string in bytes? ' write(*,space)size(bytes) ! end program demo_codepoints_to_utf8 Results: > CODEPOINTS: 78 111 104 111 32 109 101 32 107 97 32 104 97 117 ... > 8217 111 108 105 > 48 4E 6F 68 6F 20 6D 65 20 6B 61 20 68 61 75 2019 6F 6C 69 > STRING:Noho me ka hau’oli > How long is this string in glyphs? > 18 > How long is this string in bytes? > 20 > STRING:Noho me ka hau’oli > How long is this string in glyphs? > 18 > How long is this string in bytes? > 20 SEE ALSO functions that perform operations on character strings: • elemental: adjustl(3), adjustr(3), index(3), scan(3), verify(3) • non-elemental: len_trim(3), repeat(3), trim(3), codepoints_to_utf8(3), utf8_to_codepoints(3) AUTHOR • John S. Urban • Francois Jacq - enhancements from Francois Jacq, 2025-08 LICENSE MIT May 05, 2026 codepoints_to_utf8(3m_unicode) escape(3m_unicode) escape(3m_unicode) NAME ESCAPE(3f) - [M_unicode:CONVERSION] expand C++ escape sequences (LICENSE:MIT) SYNOPSIS function escape(line,protect) result(out) type(unicode_type),intent(in) :: line ! or character(len=*),intent(in) :: line type(unicode_type),intent(in),optional :: protect ! or character(len=1),intent(in),optional :: protect type(unicode_type) :: out DESCRIPTION ESCAPE(3) expands commonly used C++ escape sequences that represent glyphs or control characters. Escape sequences \ backslash a alert (BEL) -- g is an alias for a b backspace c suppress further output e escape f form feed n new line r carriage return t horizontal tab v vertical tab " double quote for compatibility with C strings ' single quote for compatibility with C strings ? used to avoid trigraphs oNNN byte with octal value NNN (1 to 3 digits) [0-7][0-7]* digits will be assumed an octal value till a non-octal value character is encountered dNNN byte with decimal value NNN (3 digits) xHHHHH... byte with hexadecimal value that proceeds to the first non-hexidecimal character or end of line. hHHHHH... "h" is an alias for "x" uZZZZ translate Unicode codepoint value to bytes UZZZZZZZZ translate Unicode codepoint value to bytes The default escape character is the backslash, but this may be changed using the optional parameter PROTECT. OPTIONS LINE An ASCII bytestream optionally containing UTF-8 encoded data or a UNICODE_TYPE() string to convert to an ASCII string containing C-style backslash escape sequences. PROTECT A single character designating the escape prefix. Defaults to a backslash ("\") EXAMPLES Sample Program: program demo_escape ! demonstrate filter to expand C-like escape sequences in input lines use iso_fortran_env, only : stdout => output_unit use M_unicode, only : ut=>unicode_type,ch=>character,len,escape use M_unicode, only : assignment(=), trim implicit none type(ut),allocatable :: poem(:) !type(ut) :: test(5) type(ut),allocatable :: test(:) integer :: i ! ! “The Crow and the Fox” by Jean de la Fontaine write(stdout,'(a,/)') & 'Le Corbeau et le Renard -- Jean de la Fontaine' ! poem=[& ut( 'Le Corbeau et le Renard' ),& ut( '' ),& ut( 'Ma\u00EEtre Corbeau, sur un arbre perch\u00E9,' ),& ut( 'Tenait en son bec un fromage.' ),& ut( 'Ma\u00EEtre Renard, par l\u2019odeur all\u00E9ch\u00E9,' ),& ut( 'Lui tint \U000000E0 peu pr\U000000E8s ce langage :' ),& ut( '\U000000ABH\U000000E9 ! bonjour, Monsieur du Corbeau.' ),& ut( 'Que vous \U000000EAtes joli ! que vous me semblez beau !' ),& ut( 'Sans mentir, si votre ramage' ),& ut( 'Se rapporte \U000000E0 votre plumage,' ),& ut( 'Vous \xEAtes le Ph\xE9nix des h\xF4tes de ces bois.\xBB' ),& ut( 'A ces mots le Corbeau ne se sent pas de joie ;' ),& ut( 'Et pour montrer sa belle voix,' ),& ut( 'Il ouvre un large bec, laisse tomber sa proie.' ),& ut( 'Le Renard s\u2019en saisit, et dit : \xABMon bon Monsieur,'),& ut( 'Apprenez que tout flatteur' ),& ut( 'Vit aux d\xE9pens de celui qui l\U00002019\u00E9coute :' ),& ut( 'Cette le\xE7on vaut bien un fromage, sans doute.\xBB' ),& ut( 'Le Corbeau, honteux et confus,' ),& ut( & 'Jura, mais un peu tard, qu\u2019on ne l\u2019y prendrait plus.'),& ut( ' -- Jean de la Fontaine')] ! poem=escape(poem) write(stdout,'(g0)')ch(poem) ! test=[ & '\e[H\e[2J ',& ! home cursor and clear screen ! on ANSI terminals '\tABC\tabc ',& ! write some tabs in the output '\tA\a ',& ! ring bell at end if supported '\nONE\nTWO\nTHREE ',& ! place one word per line '\\ '] test=trim(escape(test)) write(*,'(a)')(test(i)%character(),i=1,size(test)) ! end program demo_escape Partial Results (with nonprintable characters shown visible): > ^[[H^[[2J > ^IABC^Iabc > ^IA^G > > ONE > TWO > THREE > \ AUTHOR John S. Urban LICENSE MIT May 05, 2026 escape(3m_unicode) expand_html(3m_unicode) expand_html(3m_unicode) NAME expand_html(3f) - [M_unicode:ENCODE] expand HTML character entities ("&NAME;" strings) (LICENSE:MIT) SYNOPSIS pure elemental function expand_html(str) result (string) type(unicode_type),intent(in),optional :: str ! or character(len=*),intent(in),optional :: str type(unicode_type) :: string DESCRIPTION expand_html(string) returns a copy of the input string with all HTML character entities ( "&NAME;" and "&#NUMBER;") expanded OPTIONS str string containing HTML character entities to expand. If STR is not present a table of all the HTML character entity names and the characer they represent and its decimal and hexadecimal value(s) is written to stdout RETURNS expand_html copy of the input string with all HTML character entities expanded EXAMPLES Sample program: program demo_expand_html use iso_fortran_env, only : stdout => output_unit use M_unicode, only : expand_html, unicode_type, assignment(=) use M_unicode, only : ut => unicode_type, operator(==) use M_unicode, only : ch => character implicit none character(len=*),parameter :: g='(*(g0))' integer :: i character(len=*),parameter :: data(*)=[character(len=132) :: & ' HTML Character Entity Test Page', & ' Description Entity Entity Rendered ', & ' Name Number Result', & 'Less than &lt; &#60; <<<', & 'Greater than &gt; &#62; >>>', & 'Ampersand &amp; &#38; &&&', & 'Copyright &copy; &#169; ©©©', & 'Registered &reg; &#174; ®®®', & 'Trademark &trade; &#8482; ™™™', & 'Euro &euro; &#8364; €€€', & 'Pound &pound; &#163; £££', & 'Non-breaking space &nbsp; &#160; Before   After'] do i=1,size(data) write(stdout,g)trim(ch(expand_html(data(i)))) enddo end program demo_expand_html Expected output > HTML Character Entity Test Page > Description Entity Entity Rendered > Name Number Result > Less than < < <<< > Greater than > > >>> > Ampersand & & &&& > Copyright © © ©©© > Registered ® ® ®®® > Trademark ™ ™ ™™™ > Euro € € €€€ > Pound £ £ £££ > Non-breaking space     Before   After AUTHOR John S. Urban LICENSE MIT May 05, 2026 expand_html(3m_unicode) adjustl(3m_unicode) adjustl(3m_unicode) NAME ADJUSTL(3f) - [M_unicode:WHITESPACE] Left-justified a string (LICENSE:MIT) SYNOPSIS result = adjustl(string,glyphs) function adjustl(string,glyphs) result(out) type(unicode_type),intent(in) :: string integer,intent(in),optional :: glyphs type(unicode_type) :: out CHARACTERISTICS • STRING is a string variable of type(unicode_type) • GLYPHS is a default integer • The return value is a string variable of type(unicode_type) DESCRIPTION adjustl(3) will left-justify a string by removing leading spaces. Spaces are inserted at the end of the string as needed to keep the number of glyphs on output the same as the number on input unless overridden by the GLYPHS parameter. OPTIONS • STRING : the string to left-justify • GLYPHS : the length of the output in glyphs RESULT A copy of STRING where leading spaces are removed and the same number of spaces are inserted on the end of STRING unless GLYPHS is specified. Note using GLYPHS can cause in string truncation. EXAMPLES Sample program: program demo_adjustl use M_unicode, only : ut=>unicode_type use M_unicode, only : ch=>character use M_unicode, only : adjustl, trim, len_trim, verify use M_unicode, only : write(formatted) use M_unicode, only : assignment(=) implicit none type(ut) :: usample, uout integer :: istart, iend character(len=*),parameter :: adt = '(a,"[",DT,"]")' ! ! basic use usample=' sample string ' write(*,adt) 'original: ',usample ! ! note a string stays the same length ! and is not trimmed by just an adjustl(3) call. write(*,adt) 'adjusted: ',adjustl(usample) ! ! a fixed‐length string can be trimmed using trim(3) uout=trim(adjustl(usample)) write(*,adt) 'trimmed: ',uout ! ! or alternatively you can select a substring without adjusting istart= max(1,verify(usample, ' ')) ! first non‐blank character iend = len_trim(usample) write(*,adt) 'substring:',usample%sub(istart,iend) ! write(*,adt) 'substring:',adjustl(usample,30) write(*,adt) 'substring:',adjustl(usample,20) write(*,adt) 'substring:',adjustl(usample,10) write(*,adt) 'substring:',adjustl(usample,0) end program demo_adjustl Results: > original: [ sample string ] > adjusted: [sample string ] > trimmed: [sample string] > substring:[sample string] SEE ALSO ADJUSTR(3), TRIM(3) AUTHOR John S. Urban LICENSE MIT May 05, 2026 adjustl(3m_unicode) add_backslash(3m_unicode) add_backslash(3m_unicode) NAME ADD_BACKSLASH(3f) - [M_unicode:CONVERSION] Convert UTF-8 encoded data to ASCII-7 C-style backslash escape sequences (LICENSE:MIT) SYNOPSIS function add_backslash(line) result(out) type(unicode_type),intent(in) :: line or character(len=*),intent(in) :: line character(len=:),allocatable :: out DESCRIPTION ADD_BACKSLASH(3) replaces non-printable ASCII-7 characters and UTF-8 non- ASCII-7 characters to ASCII-7 escape sequences. Escape sequences will all be preceded by a backslash. The conversion rules are intended to comply with the conversions performed by the gfortran -fbackslash extension and C-style escape sequences. Escape sequences \ backslash 0 null a alert (BEL) -- g is an alias for a b backspace e escape f form feed n new line r carriage return t horizontal tab v vertical tab xHH one-byte characters with hexadecimal value HH (2 digits) uZZZZ Unicode codepoint value from FF+1 to FFFF. UZZZZZZZZ Unicode codepoint value from FFFF+1 to FFFFFFFF. OPTIONS LINE An ASCII bytestream optionally containing UTF-8 encoded data or a UNICODE_TYPE() string to convert to an ASCII string containing C-style backslash escape sequences. RETURNS The return value is the input line with all characters not representing printable ASCII characters converted to their C-style backslash escape sequences. EXAMPLES Sample Program: program demo_add_backslash ! filter to replace all but printable ASCII-7 characters ! with C-like escape sequences use iso_fortran_env, only : stdout => output_unit use M_unicode, only : add_backslash use M_unicode, only : assignment(=) use M_unicode, only : ut => unicode_type implicit none character(len=:),allocatable :: poem(:) type(ut) :: uline character(len=:),allocatable :: aline integer :: i ! ! “The Crow and the Fox” by Jean de la Fontaine ! poem=[character(len=255) :: & 'Le Corbeau et le Renard ',& ' ',& 'Maître Corbeau, sur un arbre perché, ',& 'Tenait en son bec un fromage. ',& 'Maître Renard, par l’odeur alléché, ',& 'Lui tint à peu près ce langage : ',& '«Hé ! bonjour, Monsieur du Corbeau. ',& 'Que vous êtes joli ! que vous me semblez beau ! ',& 'Sans mentir, si votre ramage ',& 'Se rapporte à votre plumage, ',& 'Vous êtes le Phénix des hôtes de ces bois.» ',& 'A ces mots le Corbeau ne se sent pas de joie ; ',& 'Et pour montrer sa belle voix, ',& 'Il ouvre un large bec, laisse tomber sa proie. ',& 'Le Renard s’en saisit, et dit : «Mon bon Monsieur, ',& 'Apprenez que tout flatteur ',& 'Vit aux dépens de celui qui l’écoute : ',& 'Cette leçon vaut bien un fromage, sans doute.» ',& 'Le Corbeau, honteux et confus, ',& 'Jura, mais un peu tard, qu’on ne l’y prendrait plus. ',& ' -- Jean de la Fontaine '] do i=1,size(poem) ! convert UTF-8 to UNICODE_TYPE for demonstration purposes uline=poem(i) aline=add_backslash(uline) write(stdout,'(g0)')trim(aline) enddo do i=1,size(poem) aline=add_backslash(poem(i)) write(stdout,'(g0)')trim(aline) enddo end program demo_add_backslash AUTHOR John S. Urban LICENSE MIT May 05, 2026 add_backslash(3m_unicode)