/// <summary> /// Constructs an instance of a FormatManager using the given /// format string. /// </summary> /// <param name="formatString">A FORTRAN format string.</param> public FormatManager(string formatString) { if (formatString == "*") { formatString = string.Empty; } _formatString = formatString; _fmtLength = _formatString.Length; _plusRequired = FormatOptionalPlus.Default; _groups = new Stack(); }
/// <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; }