FTN95 for .NET provides a STRING type as an extension to standard Fortran. The keyword STRING is an alias for OBJECT('System.String') and all the features described below will also work on variables declared using the corresponding OBJECT syntax.
STRING can be used in much the same way as the standard CHARACTER type but represents a varying length string. STRING is similar to the ISO standard TYPE(ISO_VARYING_STRING) but it uses garbage collection and is UNICODE rather than ASCII.
When translating STRING objects to Fortran CHARACTER type, remember that not all UNICODE characters can be converted to ASCII. The current thread locale (set of conversion rules) is used for translation.
The standard intrinsic functions CHAR and LEN are extended to allow STRING type arguments. Although the standard intrinsic CHAR always returns a single character, when the extended CHAR is called with a STRING argument it returns a Fortran character string that is just large enough to hold the argument.
The FTN95 intrinsic function STRING@ is used to convert a value of almost any type into a STRING:
a) When you supply an argument that is a Fortran character string, STRING@ returns the string that includes any trailing spaces used for padding. Trailing spaces can be removed by first using TRIM.
b) For all other types STRING@ uses the ToString method of the class System.String to do the conversion. This means that numbers are converted to STRINGs using default .NET formats and this will be different from the result of using a list directed Fortran WRITE statement. Alternatively a WRITE statement can be used to convert a number to a Fortran string before using STRING@ to convert the result.
c) For a COMPLEX argument the ToString method is called on the real and imaginary parts.
d) Conversion from LOGICAL is not implemented but a construction of the form STRING@(MERGE('T' , 'F', value<EXP>)) may be appropriate.
The substring and concatenation operators (e.g. S(1:10)//'Hello') can be applied to STRING variables. Note that mixed STRING and CHARACTER expressions can be concatenated and assigned. FTN95 automatically promotes all CHARACTER objects to STRING before the concatenation/assignment. This means that these operators follow the System.String semantics rather than the CHARACTER ones (i.e. there is no space extension or truncation of any results.). Explicitly casting the STRING arguments using the extended CHAR function allows the standard Fortran semantics to be used with concatenation (e.g. CHAR(S(1:10))//'Hello').
Note that the STRING type is an immutable type. For example, the following is illegal:
STRING :: s
s = '123456'
s(1:4) = 'abcd'
The last line should be replaced by:
s = 'abcd' // s(5:)
Here is an example that uses the STRING type:
PROGRAM test
STRING :: s
s = 'Hello, everyone'
s = s(1:7) // 'World.'
CALL foo(CHAR(s), LEN(s))
END PROGRAM test
SUBROUTINE foo(c, j)
CHARACTER(LEN=*) :: c
INTEGER :: j
PRINT *, c, j, 1.0
CALL bar(STRING@(c), STRING@(j), STRING@(1.0, 'f'))
END SUBROUTINE foo
SUBROUTINE bar(s, t, u)
STRING :: s, t, u
PRINT '(3(1X, A))', CHAR(s), CHAR(t), CHAR(u)
END SUBROUTINE bar
This produces the output:
Hello, World. 13 1.00000
Hello, World. 13 1.00
Note that in STRING@(1.0,'f') there is a second argument that specifies a floating point format, otherwise the .NET Framework would have used "1" rather than "1.00" to represent 1.0. This also applies to COMPLEX arguments for STRING@. See the .NET Framework documentation for a complete list of possible formats.
When using COMPLEX numbers and an explicit format string, you must specify the real and imaginary parts using '{0}' and '{1}' respectively. For example, to force the printing of a decimal point, use '({0:f}, {1:f})'. This contrasts with the REAL case above which does not use curly brackets.
Note also that you must have explicit casts (using the CHAR intrinsic) to WRITE or PRINT a STRING. This conversion is read-only, so you cannot directly READ a STRING for example. To READ a STRING you must first READ it into a temporary CHARACTER string and then convert this to a STRING.
STRING@ is very useful for accessing values for .NET types that are not supported by FTN95, such as OBJECT('System.Decimal'). However, when converting to a CHARACTER string, it is better to avoid constructs like:
chr = CHAR(STRING@(obj))
Code generation is improved if you use a temporary STRING variable instead as follows:
STRING :: tmp
tmp = STRING@(obj)
chr = CHAR(tmp)
After this assignment, the expression (STRING@(chr) == tmp) will not necessarily evaluate to TRUE because tmp is a UNICODE string whilst chr is an ASCII string.
Limitations of the STRING type
1. FTN95 uses one-based indexing for the substring operator, but the
System.String methods use zero-based indexing.
2. You cannot use DATA, BLOCK DATA or an initialisation expression to initialise a STRING object.
3. There is no intrinsic or I/O support for STRING apart from LEN and CHAR.
4. FTN95 passes all STRING arguments as if they were declared in C# as "ref string".