Index: libcfa/src/collections/string_res.cfa
===================================================================
--- libcfa/src/collections/string_res.cfa	(revision 64c4b4ddd59133fe62b3f4efdef17ebc2c51119b)
+++ libcfa/src/collections/string_res.cfa	(revision 68cf1d49c3f5d0ac8afe843ba072540cf6db0db7)
@@ -10,6 +10,6 @@
 // Created On       : Fri Sep 03 11:00:00 2021
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Tue Jan 16 22:19:27 2024
-// Update Count     : 35
+// Last Modified On : Mon Jan 22 23:12:42 2024
+// Update Count     : 43
 //
 
@@ -263,5 +263,5 @@
 	bool cont = false;
 
-	_Istream_Cstr cf = { cstr, (_Istream_str_base)f };
+	_Istream_Cwidth cf = { cstr, (_Istream_str_base)f };
 	if ( ! cf.flags.rwd ) cf.wd = wd;
 
Index: libcfa/src/iostream.cfa
===================================================================
--- libcfa/src/iostream.cfa	(revision 64c4b4ddd59133fe62b3f4efdef17ebc2c51119b)
+++ libcfa/src/iostream.cfa	(revision 68cf1d49c3f5d0ac8afe843ba072540cf6db0db7)
@@ -10,6 +10,6 @@
 // Created On       : Wed May 27 17:56:53 2015
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Jan  3 10:53:13 2024
-// Update Count     : 1898
+// Last Modified On : Thu Jan 25 08:39:31 2024
+// Update Count     : 1901
 //
 
@@ -984,86 +984,61 @@
 	}
 
-	istype & ?|?( istype & is, _Istream_Cquoted f ) with( f ) {
-		char fmtstr[32];								// storage scanset and format codes
+	istype & ?|?( istype & is, _Istream_Cquoted f ) with( f.cstr ) {
+		int args;
+	  fini: {
+			args = fmt( is, "%*[ \f\n\r\t\v]" );		// remove leading whitespace
+			if ( eof( is ) ) break fini;
+			char rfmt[4] = { delimiters[0], '%', 'n', '\0' };
+			int len = 0;								// may not be set in fmt
+			args = fmt( is, rfmt, &len );				// remove leading quote
+			if ( len == 0 || eof( is ) ) break fini;
+
+			// Change the remainder of the read into a getline by reseting the closing delimiter.
+			if ( delimiters[1] != '\0' ) {
+				delimiters[0] = delimiters[1];
+				delimiters[1] = '\0';
+			} // if
+			flags.delimiter = true;
+			return is | *(_Istream_Cstr *)&f;
+		} // fini
+		if ( ! flags.ignore && args == 0 ) s[0] = '\0';	// read failed => no pattern match => set string to null
+		if ( args == 1 && eof( is ) ) {					// data but scan ended at EOF
+			clear( is );								// => reset EOF => detect again on next read
+		} // if
+		return is;
+	}
+
+	istype & ?|?( istype & is, _Istream_Cstr f ) with( f.cstr ) {
+		const char * scanset;
+		size_t nscanset = 0;
+		if ( flags.delimiter ) scanset = delimiters;	// getline ?
+		else scanset = f.cstr.scanset;
+		if ( scanset ) nscanset = strlen( scanset );
+
+		char fmtstr[nscanset + 32];						// storage for scanset and format codes
 		fmtstr[0] = '%';
-
 		int pos = 1;
 		int args;
 		bool check = true;
 
-		if ( cstr.flags.ignore ) { check = false; fmtstr[1] = '*'; pos += 1; }
-		int rwd = cstr.wd;
-		if ( cstr.wd != -1 ) {						// => just ignore versus ignore with width
+		if ( flags.ignore ) { check = false; fmtstr[1] = '*'; pos += 1; }
+		int rwd = wd;
+		if ( wd != -1 ) {								// => just ignore versus ignore with width
 			// wd is buffer bytes available (for input chars + null terminator)
 			// rwd is count of input chars
 			// no maximum width necessary because text ignored => width is read width
-			if ( cstr.flags.rwd ) check = false;
-			else rwd = cstr.wd - 1;
+			if ( flags.rwd ) check = false;
+			else rwd = wd - 1;
+			assert( rwd > 0 );
 			pos += sprintf( &fmtstr[pos], "%d", rwd );
 		} // if
 
-		int len = 0;									// may not be set in fmt
-		char enddelim;
-		if ( ! cstr.flags.inex ) {						// => quoted getline
-			args = fmt( is, "%*[ \f\n\r\t\v]" );		// remove leading whitespace
-			if ( eof( is ) ) goto Eof;
-			char rfmt[4] = { cstr.delimiters[0], '%', 'n', '\0' };
-			args = fmt( is, rfmt, &len );				// remove leading quote
-			if ( len == 0 || eof( is ) ) goto Eof;
-		} // if
-		enddelim = cstr.delimiters[1] == '\0' ? cstr.delimiters[0] : cstr.delimiters[1];
-		sprintf( &fmtstr[pos], "[^%c]%%n", enddelim );
-		if ( cstr.flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
-		else args = fmt( is, fmtstr, cstr.s, &len );
-		if ( check && len == rwd && ! eof( is ) ) {		// might not fit
-			char peek;
-			fmt( is, "%c", &peek );						// check for delimiter
-			if ( ! eof( is ) ) {
-				if ( peek != enddelim ) {
-					ungetc( is, peek );
-					throwResume ExceptionInst( cstring_length );
-				} // if
-			} // if
-		} else fmt( is, "%*c" );						// remove delimiter
-	  Eof: ;
-		if ( rwd > 0 && args == 0 ) cstr.s[0] = '\0';	// read failed => no pattern match => set string to null
-		if ( args == 1 && eof( is ) ) {					// data but scan ended at EOF
-			clear( is );								// => reset EOF => detect again on next read
-		} // if
-		return is;
-	}
-
-	istype & ?|?( istype & is, _Istream_Cstr f ) with( f ) {
-		const char * scanset;
-		size_t nscanset = 0;
-		if ( flags.delimiter ) scanset = delimiters;	// getline ?
-		else scanset = f.scanset;
-		if ( scanset ) nscanset = strlen( scanset );
-
-		char fmtstr[nscanset + 32];						// storage for scanset and format codes
-		fmtstr[0] = '%';
-
-		int pos = 1;
-		int args;
-		bool check = true;
-
-		if ( f.flags.ignore ) { check = false; fmtstr[1] = '*'; pos += 1; }
-		int rwd = f.wd;
-		if ( f.wd != -1 ) {								// => just ignore versus ignore with width
-			// wd is buffer bytes available (for input chars + null terminator)
-			// rwd is count of input chars
-			// no maximum width necessary because text ignored => width is read width
-			if ( f.flags.rwd ) check = false;
-			else rwd = f.wd - 1;
-			pos += sprintf( &fmtstr[pos], "%d", rwd );
-		} // if
-
 		if ( ! scanset ) {								// %s, %*s, %ws, %*ws
-			// fprintf( stderr, "cstr %s\n", f.s );
+			// fprintf( stderr, "cstr %s\n", s );
 			strcpy( &fmtstr[pos], "s%n" );
 			int len = 0;								// may not be set in fmt
-			if ( f.flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
-			else args = fmt( is, fmtstr, f.s, &len );
-			// fprintf( stderr, "cstr %s %d %d %d %s\n", fmtstr, args, len, f.wd, f.s );
+			if ( flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
+			else args = fmt( is, fmtstr, s, &len );
+			// fprintf( stderr, "cstr %s %d %d %d %s\n", fmtstr, args, len, wd, s );
 			if ( check && len >= rwd && ! eof( is ) ) {	// might not fit
 				char peek;
@@ -1076,18 +1051,17 @@
 			} // if
 			// FIX ME: CFA strings need to be modified to NOT change the argument for this case, then this can be removed.
-			if ( ! f.flags.ignore && rwd > 0 && args == 0 ) f.s[0]= '\0';	// read failed => no pattern match => set string to null
+			if ( ! flags.ignore && args == 0 ) s[0]= '\0'; // read failed => no pattern match => set string to null
 		} else {
-			if ( f.flags.delimiter ) {					// getline
+			if ( flags.delimiter ) {					// getline
 				int len = 0;							// may not be set in fmt
-				sprintf( &fmtstr[pos], "[^%c]%%n", f.delimiters[0] );
-				if ( f.flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
-				else args = fmt( is, fmtstr, f.s, &len );
+				sprintf( &fmtstr[pos], "[^%c]%%n", delimiters[0] );
+				if ( flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
+				else args = fmt( is, fmtstr, s, &len );
 				if ( check && len == rwd && ! eof( is ) ) {	// might not fit
-					fmtstr[0] = f.delimiters[0]; fmtstr[1] = '%'; fmtstr[2] = 'n'; fmtstr[3] = '\0';
-					fmt( is, fmtstr, &len );			// remove delimiter
+					char peek;
+					fmt( is, "%c", &peek );				// check for delimiter
 					if ( ! eof( is ) ) {
-//						if ( peek != f.delimiter[0] ) {
-						if ( len != 1 ) {
-//							ungetc( is, peek );
+						if ( peek != delimiters[0] ) {
+							ungetc( is, peek );
 							throwResume ExceptionInst( cstring_length );
 						} // if
@@ -1097,10 +1071,10 @@
 				// incl %[xxx],  %*[xxx],  %w[xxx],  %*w[xxx]
 				// excl %[^xxx], %*[^xxx], %w[^xxx], %*w[^xxx]
-				sprintf( &fmtstr[pos], "[%s%s]%%n", f.flags.inex ? "^" : "", scanset );
-				// fprintf( stderr, "incl/excl %s %d\n", fmtstr, f.wd );
+				sprintf( &fmtstr[pos], "[%s%s]%%n", flags.inex ? "^" : "", scanset );
+				// fprintf( stderr, "incl/excl %s %d\n", fmtstr, wd );
 				int len = 0;							// may not be set in fmt
-				if ( f.flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
-				else args = fmt( is, fmtstr, f.s, &len );
-				// fprintf( stderr, "incl/excl %s \"%s\" %d %d %d %d %d %c\n", fmtstr, f.s, args, f.wd, len, eof( is ), check, f.s[f.wd] );
+				if ( flags.ignore ) args = fmt( is, fmtstr, &len ); // no string argument for '*'
+				else args = fmt( is, fmtstr, s, &len );
+				// fprintf( stderr, "incl/excl %s \"%s\" %d %d %d %d %d %c\n", fmtstr, s, args, wd, len, eof( is ), check, s[wd] );
 				if ( check && len == rwd && ! eof( is ) ) {	// might not fit
 					// fprintf( stderr, "overflow\n" );
@@ -1110,9 +1084,9 @@
 					if ( ! eof( is ) ) {
 						ungetc( is, peek );
-						if ( f.flags.inex ^ strchr( f.scanset, peek ) != 0p ) throwResume ExceptionInst( cstring_length );
+						if ( flags.inex ^ strchr( scanset, peek ) != 0p ) throwResume ExceptionInst( cstring_length );
 					} // if
 				} // if
 			} // if
-			if ( ! f.flags.ignore && rwd > 0 && args == 0 ) f.s[0]= '\0';	// read failed => no pattern match => set string to null
+			if ( ! flags.ignore && args == 0 ) s[0]= '\0'; // read failed => no pattern match => set string to null
 		} // if
 		if ( args == 1 && eof( is ) ) {					// data but scan ended at EOF
@@ -1123,6 +1097,11 @@
 	} // ?|?
 
-	istype & ?|?( istype & is, _Istream_Char f ) {
-		fmt( is, "%*c" );								// argument variable unused
+	istype & ?|?( istype & is, _Istream_Char f ) with(f) {
+		if ( ignore ) {
+			fmt( is, "%*c" );							// argument variable unused
+		} else {
+			int len = -1, args = fmt( is, fmt, &c, &len );
+			if ( args != -1 && len == -1 ) throwResume ExceptionInst( missing_data );
+		} // if
 		return is;
 	} // ?|?
Index: libcfa/src/iostream.hfa
===================================================================
--- libcfa/src/iostream.hfa	(revision 64c4b4ddd59133fe62b3f4efdef17ebc2c51119b)
+++ libcfa/src/iostream.hfa	(revision 68cf1d49c3f5d0ac8afe843ba072540cf6db0db7)
@@ -10,6 +10,6 @@
 // Created On       : Wed May 27 17:56:53 2015
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Sun Jan 21 22:42:40 2024
-// Update Count     : 655
+// Last Modified On : Thu Jan 25 08:39:04 2024
+// Update Count     : 696
 //
 
@@ -404,37 +404,51 @@
 }; // _Istream_str_base
 
-struct _Istream_Cstr {
+struct _Istream_Cwidth {
 	char * s;
 	inline _Istream_str_base;
 }; // _Istream_Cstr
 
+// Restrict nesting of input manipulators to those combinations that make sense.
+
+struct _Istream_Cstr {
+	_Istream_Cwidth cstr;
+}; // _Istream_Cstr
+
 struct _Istream_Cquoted {
-	_Istream_Cstr cstr;
+	_Istream_Cwidth cstr;
 }; // _Istream_Cquoted
 
 static inline {
-	// width must include room for null terminator
-	_Istream_Cstr wdi( unsigned int wd, char s[] ) { return (_Istream_Cstr)@{ .s : s, { {.scanset : 0p}, .wd : wd, {.all : 0} } }; }
-	_Istream_Cstr wdi( unsigned int wd, unsigned int rwd, char s[] ) {
-		if ( wd <= rwd ) throw (cstring_length){ &cstring_length_vt };
-		return (_Istream_Cstr)@{ .s : s, { {.scanset : 0p}, .wd : rwd, {.flags.rwd : true} } };
+	// width must include room for null terminator, (gcc) scanf does not allow a 0 width => wd > 1 (1 char and null) and rd > 0 (1 char);
+	_Istream_Cwidth wdi( unsigned int wd, char s[] ) {
+		if ( wd <= 1 ) throw (cstring_length){ &cstring_length_vt }; // minimum 1 character and null terminator
+		return (_Istream_Cwidth)@{ .s : s, { {.scanset : 0p}, .wd : wd, {.all : 0} } };
 	}
-	_Istream_Cquoted & quoted( _Istream_Cstr & fmt, const char Ldelimiter = '"', const char Rdelimiter = '\0' ) {
-		fmt.delimiters[0] = Ldelimiter;  fmt.delimiters[1] = Rdelimiter;  fmt.delimiters[2] = '\0';
-		return (_Istream_Cquoted &)fmt;
+	_Istream_Cwidth wdi( unsigned int wd, unsigned int rwd, char s[] ) {
+		if ( wd <= 1 || wd <= rwd ) throw (cstring_length){ &cstring_length_vt }; // minimum 1 character, null terminator, plus subset
+		return (_Istream_Cwidth)@{ .s : s, { {.scanset : 0p}, .wd : rwd, {.flags.rwd : true} } };
 	}
-	_Istream_Cstr & getline( _Istream_Cstr & fmt, const char delimiter = '\n' ) {
-		fmt.delimiters[0] = delimiter; fmt.delimiters[1] = '\0'; fmt.flags.delimiter = true; fmt.flags.inex = true; return fmt;
+	_Istream_Cquoted & quoted( _Istream_Cwidth & f, const char Ldelimiter = '"', const char Rdelimiter = '\0' ) {
+		f.delimiters[0] = Ldelimiter;  f.delimiters[1] = Rdelimiter;  f.delimiters[2] = '\0';
+		return (_Istream_Cquoted &)f;
 	}
-	_Istream_Cstr & incl( const char scanset[], _Istream_Cstr & fmt ) { fmt.scanset = scanset; fmt.flags.inex = false; return fmt; }
-	_Istream_Cstr & excl( const char scanset[], _Istream_Cstr & fmt ) { fmt.scanset = scanset; fmt.flags.inex = true; return fmt; }
-	_Istream_Cstr ignore( char s[] ) { return (_Istream_Cstr)@{ .s : s, { {.scanset : 0p}, .wd : -1, {.flags.ignore : true} } }; }
-	_Istream_Cstr & ignore( _Istream_Cstr & fmt ) { fmt.flags.ignore = true; return fmt; }
+	_Istream_Cstr & getline( _Istream_Cwidth & f, const char delimiter = '\n' ) {
+		f.delimiters[0] = delimiter; f.delimiters[1] = '\0'; f.flags.delimiter = true; return (_Istream_Cstr &)f;
+	}
+	_Istream_Cstr & incl( const char scanset[], _Istream_Cwidth & f ) { f.scanset = scanset; f.flags.inex = false; return (_Istream_Cstr &)f; }
+	_Istream_Cstr & excl( const char scanset[], _Istream_Cwidth & f ) { f.scanset = scanset; f.flags.inex = true; return (_Istream_Cstr &)f; }
+	_Istream_Cstr ignore( char s[] ) { return (_Istream_Cwidth)@{ .s : s, { {.scanset : 0p}, .wd : -1, {.flags.ignore : true} } }; }
+	_Istream_Cstr & ignore( _Istream_Cwidth & f ) { f.flags.ignore = true; return (_Istream_Cstr &)f; }
+	_Istream_Cquoted & ignore( _Istream_Cquoted & f ) { f.cstr.flags.ignore = true; return (_Istream_Cquoted &)f; }
+	_Istream_Cstr & ignore( _Istream_Cstr & f ) { f.cstr.flags.ignore = true; return (_Istream_Cstr &)f; }
 } // distribution
 
 forall( istype & | basic_istream( istype ) ) {
-	istype & ?|?( istype & is, _Istream_Cstr f );
 	istype & ?|?( istype & is, _Istream_Cskip f );
 	istype & ?|?( istype & is, _Istream_Cquoted f );
+	istype & ?|?( istype & is, _Istream_Cstr f );
+	static inline {
+		istype & ?|?( istype & is, _Istream_Cwidth f ) { return is | *(_Istream_Cstr *)&f; }
+	} // distribution
 } // distribution
 
