/// <summary> /// Format the specified boolean value using FORTRAN L formatting rules. /// The width specifies the amount of space, in characters, into which /// the whole number must fit or otherwise the return value is a /// string consisting of '*'s. If the width is zero, the string is returned /// without any size constraint. /// </summary> /// <param name="value">A boolean value</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>A string representation of the boolean value.</returns> public static string FormatBoolean(bool value, FormatRecord record) { if (record == null) { throw new ArgumentNullException("record"); } string newString = value ? "T" : "F"; return FormatToWidth(record.FieldWidth, newString); }
/// <summary> /// Construct a FormatRecord from another instance. /// </summary> /// <param name="record">The FormatRecord to copy</param> public FormatRecord(FormatRecord record) { if (record == null) { throw new ArgumentNullException("record"); } FieldWidth = record.FieldWidth; FormatChar = record.FormatChar; Count = record.Count; Precision = record.Precision; ExponentWidth = record.ExponentWidth; PlusRequired = record.PlusRequired; RawString = record.RawString; BlanksAsZero = record.BlanksAsZero; Relative = record.Relative; }
/// <summary> /// Format the specified complex number using FORTRAN F formatting rules /// using a double precision number. The width specifies the amount of space, in /// characters, into which the whole number must fit or otherwise the return value /// is a string consisting of '*'s. If the width is zero, the string is returned /// without any size constraint. /// /// The precisionWidth is the number of units for the non-fractional part. The /// resulting number is truncated (and the exponent adjusted if appropriate) to fit /// into the given width. If the precisionWidth is zero, no truncation occurs and /// the number of digits in the non-fractional part is determined by the number. /// </summary> /// <param name="value">A complex number</param> /// <param name="recordReal">A FormatRecord containing formatting settings for the Real part</param> /// <param name="recordImg">A FormatRecord containing formatting settings for the Imaginary part</param> /// <returns>A string representation of the complex number.</returns> public static string FormatComplex(Complex value, FormatRecord recordReal, FormatRecord recordImg) { if (recordReal == null) { throw new ArgumentNullException("recordReal"); } if (recordImg == null) { throw new ArgumentNullException("recordImg"); } FormatRecord tempRecordReal = new FormatRecord(recordReal); FormatRecord tempRecordImg = new FormatRecord(recordImg); tempRecordReal.FieldWidth = 0; tempRecordImg.FieldWidth = 0; string realPart; string imgPart; switch (recordReal.FormatChar) { case 'D': realPart = FormatDouble(value.Real, tempRecordReal); break; case 'F': realPart = FormatFloat((float)value.Real, tempRecordReal); break; case 'E': realPart = FormatExponential(value.Real, 'E', tempRecordReal); break; case 'G': realPart = FormatExponential(value.Real, 'E', tempRecordReal); break; default: throw new JComRuntimeException(string.Format("Invalid format specifier {0} for COMPLEX", recordReal.FormatChar)); } switch (recordImg.FormatChar) { case 'D': imgPart = FormatDouble(value.Imaginary, tempRecordImg); break; case 'F': imgPart = FormatFloat((float)value.Imaginary, tempRecordImg); break; case 'E': imgPart = FormatExponential(value.Imaginary, 'E', tempRecordImg); break; case 'G': imgPart = FormatExponential(value.Imaginary, 'E', tempRecordImg); break; default: throw new JComRuntimeException(string.Format("Invalid format specifier {0} for COMPLEX", recordImg.FormatChar)); } if (recordReal.FieldWidth > 0 || recordImg.FieldWidth > 0) { return FormatToWidth(recordReal.FieldWidth + recordImg.FieldWidth + 2, string.Format("{0} {1}", realPart, imgPart)); } return string.Format("({0},{1})", realPart, imgPart); }
// Process any positional directions in the given record. void ProcessPosition(FormatRecord record) { int currentWidth = _writeIndex; if (record.Relative) { currentWidth += record.Count; } else { currentWidth = record.Count - 1; } if (currentWidth > _writeMaxIndex) { _writeIndex = _writeMaxIndex; WriteChar(' ', currentWidth - _writeMaxIndex); } else { _writeIndex = currentWidth; } }
// Format a string as specified by an "A" record. string FormatString(string value, FormatRecord record) { string svalue = value; if (record.FieldWidth > 0) { if (value.Length > record.FieldWidth) { svalue = value.Substring(0, record.FieldWidth); } else { svalue = value.PadLeft(record.FieldWidth); } } return svalue; }
/// <summary> /// Initializes a new instance of the <see cref="JComLib.FormatParser"/> class /// using the specified FormatRecord to control the number parsing. /// </summary> /// <param name="buffer">The string to parse</param> /// <param name="record">The FormatRecord to use</param> public FormatParser(string buffer, FormatRecord record) { _buffer = buffer; _blankAsZero = (record != null) && record.BlanksAsZero; // Skip initial whitespace in the buffer while (_index < _buffer.Length && Char.IsWhiteSpace(_buffer[_index])) { ++_index; } }
// Do post-floating point number formatting static string FormatFloatToWidth(FormatRecord record, string newString) { if (record.PlusRequired == FormatOptionalPlus.Always) { newString = "+" + newString; } if (record.FieldWidth > 0) { int length = newString.Length; if (newString.StartsWith("0.") && length == record.FieldWidth + 1) { newString = newString.Substring(1); } if (newString.StartsWith("-0.") && length == record.FieldWidth + 1) { newString = "-." + newString.Substring(3); } } return FormatToWidth(record.FieldWidth, newString); }
/// <summary> /// Returns the next FormatRecord from the string or null if /// there are no further records to retrieve. /// </summary> /// <returns>A FormatRecord, or null if we reached the end</returns> public FormatRecord Next() { // Repeat the last record if we've still some // more to go. if (_cRepeat > 1) { --_cRepeat; return _lastRecord; } // Otherwise find the next record and return that. StringBuilder str = new StringBuilder(); bool inQuote = false; while (_charIndex < _fmtLength) { char ch = NextChar(); if (ch == '"' || ch == '\'') { inQuote = !inQuote; ++_charIndex; } else if (inQuote) { str.Append(ch); ++_charIndex; } else { if (ch == ',' || Char.IsWhiteSpace(ch)) { ++_charIndex; continue; } // If we've gathered some raw string up to here // then return that now. if (str.Length > 0) { _lastRecord = new FormatRecord(); _lastRecord.RawString = str.ToString(); return _lastRecord; } // Remember this offset for _lastFormatGroup later int markedIndex = _charIndex-1; // Check and validate any repeat specifier. Note that X, P and H are required to have // a value preceding them. It's just not treated as repeat. bool hasPrefixValue = Char.IsDigit(ch) || ch == '-' || ch == '+'; int prefixValue = ExtractNumber(1); do { ch = NextChar(); ++_charIndex; } while (Char.IsWhiteSpace(ch)); char formatChar = ch; // Valid format character? if ("IFEGLA:/()TXPSHDB".IndexOf(formatChar) < 0) { throw new JComRuntimeException(string.Format("Unknown format specifier character '{0}'", formatChar)); } // End record? if (formatChar == ':') { return null; } // Record separator? if (formatChar == '/') { _scaleFactor = 0; _lastRecord = new FormatRecord(); _lastRecord.IsEndRecord = true; return _lastRecord; } // Group start if (formatChar == '(') { if (prefixValue < 0) { throw new JComRuntimeException("Repeat count cannot be less than 0"); } FormatGroup formatGroup = new FormatGroup(); formatGroup.GroupRepeat = prefixValue; formatGroup.GroupStartIndex = _charIndex; _groups.Push(formatGroup); // Also remember this format group start in the // case of a rescan. if (_groups.Count == 1) { _lastFormatGroup = markedIndex; } continue; } // Group end if (formatChar == ')') { FormatGroup formatGroup = (FormatGroup)_groups.Pop(); if (formatGroup == null) { throw new JComRuntimeException("Parenthesis mismatch in FORMAT statement"); } if (--formatGroup.GroupRepeat > 0) { _charIndex = formatGroup.GroupStartIndex; _groups.Push(formatGroup); } continue; } // Make sure a prefix value is specified for those format characters // that require one, and not specified for those that don't. if (formatChar == 'X' || formatChar == 'P'|| formatChar == 'H') { if (!hasPrefixValue) { throw new JComRuntimeException(String.Format("'{0}' specifier requires a value", formatChar)); } } else { if (hasPrefixValue && "IFEDGLA".IndexOf(formatChar) < 0) { throw new JComRuntimeException(String.Format("Repeat count not permitted with '{0}' specifier", formatChar)); } if (prefixValue < 0) { throw new JComRuntimeException("Repeat count cannot be less than 0"); } _cRepeat = prefixValue; } // Handle cursor positioning. The following formats are recognised: // T<n> - set the cursor position to offset <n> in the current record. // TL<n> - move the cursor back <n> characters // TR<n> - move the cursor forward <n> characters if (formatChar == 'T') { _lastRecord = new FormatRecord(); _lastRecord.FormatChar = 'T'; switch (NextChar()) { case 'L': ++_charIndex; _lastRecord.Relative = true; _lastRecord.Count = -ExtractNumber(0); return _lastRecord; case 'R': ++_charIndex; _lastRecord.Relative = true; _lastRecord.Count = ExtractNumber(0); return _lastRecord; default: _lastRecord.Count = ExtractNumber(0); return _lastRecord; } } // Handle forward cursor movement. This is pretty much the // same as TR<n>. if (formatChar == 'X') { _lastRecord = new FormatRecord(); _lastRecord.FormatChar = 'T'; _lastRecord.Relative = true; _lastRecord.Count = prefixValue; _cRepeat = 1; return _lastRecord; } // Handle scale factor. This influences subsequent // formats on the same record. if (formatChar == 'P') { _scaleFactor = prefixValue; _cRepeat = 1; continue; } // Handle blank specifier which controls whether blank // characters are ignored or treated as '0'. if (formatChar == 'B') { _blanksAsZero = NextChar() == 'Z'; ++_charIndex; continue; } // Handle positive sign specification. if (formatChar == 'S') { switch (NextChar()) { case 'P': ++_charIndex; _plusRequired = FormatOptionalPlus.Always; break; case 'S': ++_charIndex; _plusRequired = FormatOptionalPlus.Never; break; default: _plusRequired = FormatOptionalPlus.Default; break; } continue; } // Hollerith character output // The prefix value is the count of subsequent characters in the format // string that are copied literally to the output. if (formatChar == 'H') { while (prefixValue > 0 && _charIndex < _fmtLength) { str.Append(NextChar()); --prefixValue; ++_charIndex; } continue; } // If we get here then we're left with formatting characters that accept a // width and precision specifier. So parse those off. int precision = 1; int exponentWidth = 0; int fieldWidth = ExtractNumber(0); if (NextChar() == '.') { ++_charIndex; precision = ExtractNumber(0); if ((formatChar == 'E' || formatChar == 'G') && NextChar() == 'E') { ++_charIndex; exponentWidth = ExtractNumber(2); } } // We've got a full format specifier so return // that back to the caller. _lastRecord = new FormatRecord(); _lastRecord.FormatChar = formatChar; _lastRecord.FieldWidth = fieldWidth; _lastRecord.Precision = precision; _lastRecord.Count = _cRepeat; _lastRecord.ExponentWidth = exponentWidth; _lastRecord.PlusRequired = _plusRequired; _lastRecord.ScaleFactor = _scaleFactor; _lastRecord.BlanksAsZero = _blanksAsZero; return _lastRecord; } } if (str.Length > 0) { _lastRecord = new FormatRecord(); _lastRecord.RawString = str.ToString(); return _lastRecord; } if (_groups.Count > 0) { throw new JComRuntimeException("Unclosed format specifier group"); } return null; }
/// <summary> /// Parses a float from the given string. Leading blanks are ignored. /// Either '+' or '-' are accepted as sign characters and must appear after /// any leading blanks at the beginning of the string. The rest of the string /// must contain a fractional number of the format: /// /// [s][nnn].[fff][Emm] /// /// where [s] is the optional sign, [nnn] is the optional mantissa and [fff] /// is an optional fraction. If an exponent is specified, it must contain an /// exponentiation value. /// /// If no exponent is explicitly specified then any scale factor is applied to /// the number. /// /// Blanks are treated as either '0' or ignored depending on the BlanksAsZero /// flag in the record. /// </summary> /// <param name="floatString">A string containing a floating point number</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>The floating point value of the string</returns> public static float ParseFloat(string floatString, FormatRecord record) { if (floatString == null) { throw new ArgumentNullException("floatString"); } return (float)ParseDouble(floatString, record); }
/// <summary> /// Parses a double from the given string. Leading blanks are ignored. /// Either '+' or '-' are accepted as sign characters and must appear after /// any leading blanks at the beginning of the string. The rest of the string /// must contain a fractional number of the format: /// /// [s][nnn].[fff][Dmm] /// /// where [s] is the optional sign, [nnn] is the optional mantissa and [fff] /// is an optional fraction. If an exponent is specified, it must contain an /// exponentiation value. /// /// If no exponent is explicitly specified then any scale factor is applied to /// the number. /// /// Blanks are treated as either '0' or ignored depending on the BlanksAsZero /// flag in the record. /// </summary> /// <param name="doubleString">A string containing a double precision number</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>The double precision value of the string</returns> public static double ParseDouble(string doubleString, FormatRecord record) { if (doubleString == null) { throw new ArgumentNullException("doubleString"); } bool inFraction = false; bool hasExponentPart = false; int exponent = 0; long mantissaPart = 0; int sign = 1; FormatParser parser = new FormatParser(doubleString, record); char ch = parser.Next(); if (ch == '+') { ch = parser.Next(); } else if (ch == '-') { sign = -1; ch = parser.Next(); } while (ch != '\0') { if (ch == '.') { if (inFraction) { throw new JComRuntimeException("Fraction already specified"); } inFraction = true; } else { if (!Char.IsDigit(ch)) { break; } mantissaPart = (mantissaPart * 10) + (ch - '0'); if (inFraction) { --exponent; } } ch = parser.Next(); } if ("EeDd".IndexOf(ch) >= 0) { ch = parser.Next(); hasExponentPart = true; } if (hasExponentPart || ch == '+' || ch == '-') { int exponentSign = 1; int exponentPart = 0; if (ch == '+') { ch = parser.Next(); } else if (ch == '-') { exponentSign = -1; ch = parser.Next(); } if (ch == '\0') { throw new JComRuntimeException("Exponent must have a value"); } while (ch != '\0') { if (!Char.IsDigit(ch)) { throw new JComRuntimeException(string.Format("Illegal character {0} in number", ch)); } exponentPart = (exponentPart * 10) + (ch - '0'); ch = parser.Next(); } exponent += (exponentSign * exponentPart); hasExponentPart = true; } else { if (ch != '\0') { throw new JComRuntimeException(string.Format("Illegal character {0} in number", ch)); } } if (!hasExponentPart && record != null) { exponent -= record.ScaleFactor; } bool expSign = false; if (exponent < 0) { expSign = true; exponent = -exponent; } if (exponent > 511) { exponent = 511; } double doubleExponent = 1.0; for (int d = 0; exponent != 0; exponent >>= 1, ++d) { if ((exponent & 1) != 0) { doubleExponent *= powersOf10[d]; } } double fraction = mantissaPart; if (expSign) { fraction /= doubleExponent; } else { fraction *= doubleExponent; } return fraction * sign; }
/// <summary> /// Format the specified integer number using FORTRAN I formatting rules using /// an integer number. The width specifies the amount of space, in characters, /// into which the whole number must fit or otherwise the return value is a /// string consisting of '*'s. If the width is zero, the string is returned /// without any size constraint. /// /// The precision is the minimum number of leading zeroes that the number must /// contain. If this is zero, the number is not zero padded. /// </summary> /// <param name="value">An integer number</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>A string representation of the integer number.</returns> public static string FormatInteger(int value, FormatRecord record) { if (record == null) { throw new ArgumentNullException("record"); } StringBuilder str = new StringBuilder(); int tempValue = Math.Abs(value); int fieldWidth = 0; do { char ch = (char)((tempValue % 10) + 48); str.Append(ch); tempValue /= 10; ++fieldWidth; } while (tempValue > 0); while (fieldWidth < record.Precision) { str.Append('0'); ++fieldWidth; } if (value < 0) { str.Append('-'); } else { if (record.PlusRequired == FormatOptionalPlus.Always) { str.Append('+'); } } return FormatToWidth(record.FieldWidth, ReverseString(str.ToString())); }
/// <summary> /// Format the specified floating point number using FORTRAN F formatting rules /// using a floating point number. The width specifies the amount of space, in /// characters, into which the whole number must fit or otherwise the return value /// is a string consisting of '*'s. If the width is zero, the string is returned /// without any size constraint. /// /// The precision is the number of units for the non-fractional part. The /// resulting number is truncated (and the exponent adjusted if appropriate) to fit /// into the given width. If the precision is zero, no truncation occurs and /// the number of digits in the non-fractional part is determined by the number. /// </summary> /// <param name="value">A floating point number</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>A string representation of the floating point number.</returns> public static string FormatFloat(float value, FormatRecord record) { if (record == null) { throw new ArgumentNullException("record"); } // Do exponential formatting if so requested if (record.FormatChar == 'E') { return FormatExponential(value, 'E', record); } // BUGBUG: Doesn't apply the FormatOptionalPlus flag. // BUGBUG: Doesn't apply the scaling factor string formatString; if (record.FieldWidth == 0 && record.Precision == 0) { formatString = "{0:G}"; } else { formatString = "{0," + record.FieldWidth.ToString() + ":F" + record.Precision.ToString() + "}"; } return FormatFloatToWidth(record, string.Format(formatString, value)); }
/// <summary> /// Format the specified double using FORTRAN E and D formatting rules: /// /// [+] [0] . x1x2...xd exp where: /// • + signifies a plus or a minus (13.5.9) /// • x1,x2...xd are the d most significant digits of the value of the datum after rounding /// • exp is a decimal exponent. /// </summary> /// <param name="value">A double precision number number</param> /// <param name="exponentChar">The character to be used to indicate the exponent</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>A string representation of the value formatted as an exponential number.</returns> public static string FormatExponential(double value, char exponentChar, FormatRecord record) { if (record == null) { throw new ArgumentNullException("record"); } int precision = record.Precision; int leadingZeroes = 1; // Extract the exponent int exponent = GetExponent(value); double newValue = value * Math.Pow(10.0, -exponent); // BUGBUG: Doesn't apply the FormatOptionalPlus flag. while (newValue >= 1.0 || newValue < -1.0) { ++exponent; newValue = value * Math.Pow(10.0, -exponent); } if (record.ScaleFactor != 0) { newValue *= Math.Pow(10.0, record.ScaleFactor); exponent -= record.ScaleFactor; // Decimal normalization with scale factor if (record.ScaleFactor <= 0 && record.ScaleFactor > -record.ExponentWidth) { leadingZeroes = Math.Abs(record.ScaleFactor); precision = record.ExponentWidth - Math.Abs(record.ScaleFactor); } else if (record.ScaleFactor > 0 && record.ScaleFactor < record.ExponentWidth + 2) { precision = (precision - record.ScaleFactor) + 1; } } string mantissaFormat = new string('0', leadingZeroes); string fractionalFormat = new string('0', precision); string exponentPortion; if (record.ExponentWidth > 0) { string exponentFormat = new string('0', record.ExponentWidth); exponentPortion = exponentChar.ToString() + exponent.ToString("+" + exponentFormat + ";-" + exponentFormat); } else if (exponent <= 99) { exponentPortion = exponentChar.ToString() + exponent.ToString("+00;-00"); } else if (exponent < 999) { exponentPortion = exponent.ToString("+000;-000"); } else { // TODO: Run-time failure here. Exponent too big for E/D format return string.Empty; } return FormatFloatToWidth(record, newValue.ToString(mantissaFormat + "." + fractionalFormat) + exponentPortion); }
/// <summary> /// Format the specified double precision number using FORTRAN F formatting rules /// using a double precision number. The width specifies the amount of space, in /// characters, into which the whole number must fit or otherwise the return value /// is a string consisting of '*'s. If the width is zero, the string is returned /// without any size constraint. /// /// The precisionWidth is the number of units for the non-fractional part. The /// resulting number is truncated (and the exponent adjusted if appropriate) to fit /// into the given width. If the precisionWidth is zero, no truncation occurs and /// the number of digits in the non-fractional part is determined by the number. /// </summary> /// <param name="value">A floating point number</param> /// <param name="record">A FormatRecord containing formatting settings</param> /// <returns>A string representation of the floating point number.</returns> public static string FormatDouble(double value, FormatRecord record) { if (record == null) { throw new ArgumentNullException("record"); } // Do exponential formatting if so requested if (record.FormatChar == 'E') { return FormatExponential(value, 'E', record); } if (record.FormatChar == 'G') { if (value < 0.1 || value > Math.Pow(10, record.Precision)) { return FormatExponential(value, 'E', record); } } // BUGBUG: Doesn't apply the FormatOptionalPlus flag. // BUGBUG: Doesn't apply the scaling factor // BUGBUG: Exponent character should be 'D' for double. string formatString; if (record.FieldWidth == 0 && record.Precision == 0) { formatString = "{0:G}"; } else { formatString = "{0," + record.FieldWidth.ToString() + ":F" + record.Precision.ToString() + "}"; } return FormatFloatToWidth(record, string.Format(formatString, value)); }
// Make sure the value is valid for the given format record type. Throw // a run-time exception if there's a mismatch. void VerifyFormatMatch(FormatRecord record, object value) { char ch = record.FormatChar; bool match = true; switch (ch) { case 'A': match = (value is String || value is FixedString); break; case 'I': match = (value is Int32); break; case 'L': match = (value is Boolean); break; case 'F': match = (value is float || value is double || value is Complex); break; case 'G': match = (value is float || value is double || value is Complex); break; case 'E': match = (value is float || value is double || value is Complex); break; } if (!match) { string realName = value.GetType().Name; switch (realName.ToLower()) { case "int32": realName = "INTEGER"; break; case "single": realName = "REAL"; break; case "double": realName = "DOUBLE"; break; case "string": realName = "CHARACTER"; break; case "fixedstring": realName = "CHARACTER"; break; } throw new JComRuntimeException(string.Format("Format record mismatch: '{0}' specifier and {1} type", ch, realName)); } }
/// <summary> /// Parses an integer from the given string. Leading blanks are ignored. /// Either '+' or '-' are accepted as sign characters and must appear after /// any leading blanks at the beginning of the string. The rest of the string /// must contain digits or blanks. Blanks are treated as either '0' or ignored /// depending on the BlanksAsZero flag in the record. /// </summary> /// <param name="intString">A string containing an integer number</param> /// <param name="record">A FormatRecord specifier</param> /// <returns>The integer value of the string</returns> public static int ParseInteger(string intString, FormatRecord record) { if (intString == null) { throw new ArgumentNullException("intString"); } int intValue = 0; int sign = 1; FormatParser parser = new FormatParser(intString, record); char ch = parser.Next(); if (ch == '+') { ch = parser.Next(); } else if (ch == '-') { sign = -1; ch = parser.Next(); } while (ch != '\0') { if (!Char.IsDigit(ch)) { throw new JComRuntimeException(string.Format("Illegal character {0} in number", ch)); } intValue = (intValue * 10) + (ch - '0'); ch = parser.Next(); } return intValue * sign; }
// Process any positional directions in the given record. void ProcessPosition(FormatRecord record) { if (record.IsPositional && _line != null) { if (record.Relative) { _readIndex += record.Count; } else { _readIndex = record.Count; } _readIndex = Math.Max(_readIndex, 0); _readIndex = Math.Min(_readIndex, _line.Length - 1); } }