Newer
Older
DNA / corlib / System / ParseHelper.cs
@Chris Bacon Chris Bacon on 21 Jan 2012 9 KB First commit
#if !LOCALTEST

using System.Globalization;
using System.Threading;
namespace System {
	internal static class ParseHelper {

		private static bool CheckStyle(NumberStyles style, bool tryParse, ref Exception exc) {
			if ((style & NumberStyles.AllowHexSpecifier) != 0) {
				NumberStyles ne = style ^ NumberStyles.AllowHexSpecifier;
				if ((ne & NumberStyles.AllowLeadingWhite) != 0) {
					ne ^= NumberStyles.AllowLeadingWhite;
				}
				if ((ne & NumberStyles.AllowTrailingWhite) != 0) {
					ne ^= NumberStyles.AllowTrailingWhite;
				}
				if (ne != 0) {
					if (!tryParse) {
						exc = new ArgumentException(
							"With AllowHexSpecifier only " +
							"AllowLeadingWhite and AllowTrailingWhite " +
							"are permitted.");
					}
					return false;
				}
			}

			return true;
		}

		private static bool JumpOverWhite(ref int pos, string s, bool reportError, bool tryParse, ref Exception exc) {
			while (pos < s.Length && Char.IsWhiteSpace(s[pos])) {
				pos++;
			}
			if (reportError && pos >= s.Length) {
				if (!tryParse) {
					exc = GetFormatException();
				}
				return false;
			}

			return true;
		}

		private static void FindSign(ref int pos, string s, NumberFormatInfo nfi,
			ref bool foundSign, ref bool negative) {
			if ((pos + nfi.NegativeSign.Length) <= s.Length &&
				s.IndexOf(nfi.NegativeSign, pos, nfi.NegativeSign.Length) == pos) {
				negative = true;
				foundSign = true;
				pos += nfi.NegativeSign.Length;
			} else if ((pos + nfi.PositiveSign.Length) < s.Length &&
				s.IndexOf(nfi.PositiveSign, pos, nfi.PositiveSign.Length) == pos) {
				negative = false;
				pos += nfi.PositiveSign.Length;
				foundSign = true;
			}
		}

		private static void FindCurrency(ref int pos, string s, NumberFormatInfo nfi, ref bool foundCurrency) {
			if ((pos + nfi.CurrencySymbol.Length) <= s.Length &&
				 s.Substring(pos, nfi.CurrencySymbol.Length) == nfi.CurrencySymbol) {
				foundCurrency = true;
				pos += nfi.CurrencySymbol.Length;
			}
		}

		private static bool FindExponent(ref int pos, string s) {
			int i = s.IndexOfAny(new char[] { 'e', 'E' }, pos);
			if (i < 0) {
				return false;
			}
			if (++i == s.Length) {
				return false;
			}
			if (s[i] == '+' || s[i] == '-') {
				if (++i == s.Length) {
					return false;
				}
			}
			if (!Char.IsDigit(s[i])) {
				return false;
			}
			for (; i < s.Length; ++i) {
				if (!Char.IsDigit(s[i])) {
					break;
				}
			}
			pos = i;
			return true;
		}

		private static bool FindOther(ref int pos, string s, string other) {
			if ((pos + other.Length) <= s.Length && s.Substring(pos, other.Length) == other) {
				pos += other.Length;
				return true;
			}
			return false;
		}

		private static bool ValidDigit(char e, bool allowHex) {
			if (allowHex) {
				return Char.IsDigit(e) || (e >= 'A' && e <= 'F') || (e >= 'a' && e <= 'f');
			}
			return Char.IsDigit(e);
		}

		private static Exception GetFormatException() {
			return new FormatException("Input string was not in the correct format");
		}

		internal static bool Parse(string s, NumberStyles style, IFormatProvider fp, bool tryParse, out int result, out Exception exc) {
			result = 0;
			exc = null;

			if (s == null) {
				if (!tryParse) {
					exc = GetFormatException();
				}
				return false;
			}

			if (s == null) {
				if (!tryParse) {
					exc = new ArgumentNullException();
				}
				return false;
			}

			if (s.Length == 0) {
				if (!tryParse) {
					exc = GetFormatException();
				}
				return false;
			}

			NumberFormatInfo nfi;
			if (fp != null) {
				Type typeNFI = typeof(System.Globalization.NumberFormatInfo);
				nfi = (NumberFormatInfo)fp.GetFormat(typeNFI);
			} else {
				nfi = Thread.CurrentThread.CurrentCulture.NumberFormat;
			}
			if (!CheckStyle(style, tryParse, ref exc)) {
				return false;
			}
			bool AllowCurrencySymbol = (style & NumberStyles.AllowCurrencySymbol) != 0;
			bool AllowHexSpecifier = (style & NumberStyles.AllowHexSpecifier) != 0;
			bool AllowThousands = (style & NumberStyles.AllowThousands) != 0;
			bool AllowDecimalPoint = (style & NumberStyles.AllowDecimalPoint) != 0;
			bool AllowParentheses = (style & NumberStyles.AllowParentheses) != 0;
			bool AllowTrailingSign = (style & NumberStyles.AllowTrailingSign) != 0;
			bool AllowLeadingSign = (style & NumberStyles.AllowLeadingSign) != 0;
			bool AllowTrailingWhite = (style & NumberStyles.AllowTrailingWhite) != 0;
			bool AllowLeadingWhite = (style & NumberStyles.AllowLeadingWhite) != 0;
			bool AllowExponent = (style & NumberStyles.AllowExponent) != 0;

			int pos = 0;

			if (AllowLeadingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
				return false;
			}

			bool foundOpenParentheses = false;
			bool negative = false;
			bool foundSign = false;
			bool foundCurrency = false;

			// Pre-number stuff
			if (AllowParentheses && s[pos] == '(') {
				foundOpenParentheses = true;
				foundSign = true;
				negative = true; // MS always make the number negative when there parentheses
				// even when NumberFormatInfo.NumberNegativePattern != 0!!!
				pos++;
				if (AllowLeadingWhite && !!JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
					return false;
				}
				if (s.Substring(pos, nfi.NegativeSign.Length) == nfi.NegativeSign) {
					if (!tryParse) {
						exc = GetFormatException();
					}
					return false;
				}

				if (s.Substring(pos, nfi.PositiveSign.Length) == nfi.PositiveSign) {
					if (!tryParse) {
						exc = GetFormatException();
					}
					return false;
				}
			}

			if (AllowLeadingSign && !foundSign) {
				// Sign + Currency
				FindSign(ref pos, s, nfi, ref foundSign, ref negative);
				if (foundSign) {
					if (AllowLeadingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
						return false;
					}
					if (AllowCurrencySymbol) {
						FindCurrency(ref pos, s, nfi, ref foundCurrency);
						if (foundCurrency && AllowLeadingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
							return false;
						}
					}
				}
			}

			if (AllowCurrencySymbol && !foundCurrency) {
				// Currency + sign
				FindCurrency(ref pos, s, nfi, ref foundCurrency);
				if (foundCurrency) {
					if (AllowLeadingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
						return false;
					}
					if (foundCurrency) {
						if (!foundSign && AllowLeadingSign) {
							FindSign(ref pos, s, nfi, ref foundSign, ref negative);
							if (foundSign && AllowLeadingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
								return false;
							}
						}
					}
				}
			}

			int number = 0;
			int nDigits = 0;
			bool decimalPointFound = false;
			int digitValue;
			char hexDigit;

			// Number stuff
			do {
				if (!ValidDigit(s[pos], AllowHexSpecifier)) {
					if (AllowThousands && FindOther(ref pos, s, nfi.NumberGroupSeparator)) {
						continue;
					} else {
						if (!decimalPointFound && AllowDecimalPoint && FindOther(ref pos, s, nfi.NumberDecimalSeparator)) {
							decimalPointFound = true;
							continue;
						}
					}
					break;
				} else if (AllowHexSpecifier) {
					nDigits++;
					hexDigit = s[pos++];
					if (Char.IsDigit(hexDigit)) {
						digitValue = (int)(hexDigit - '0');
					} else if (Char.IsLower(hexDigit)) {
						digitValue = (int)(hexDigit - 'a' + 10);
					} else {
						digitValue = (int)(hexDigit - 'A' + 10);
					}
					uint unumber = (uint)number;
					try {
						number = (int)checked(unumber * 16u + (uint)digitValue);
					} catch (OverflowException e) {
						exc = e;
						return false;
					}
				} else if (decimalPointFound) {
					nDigits++;
					// Allows decimal point as long as it's only 
					// followed by zeroes.
					if (s[pos++] != '0') {
						if (!tryParse) {
							exc = new OverflowException("Value too large or too small.");
						}
						return false;
					}
				} else {
					nDigits++;

					try {
						// Calculations done as negative
						// (abs (MinValue) > abs (MaxValue))
						number = checked(
							number * 10 -
							(int)(s[pos++] - '0')
							);
					} catch (OverflowException) {
						if (!tryParse) {
							exc = new OverflowException("Value too large or too small.");
						}
						return false;
					}
				}
			} while (pos < s.Length);

			// Post number stuff
			if (nDigits == 0) {
				if (!tryParse) {
					exc = GetFormatException();
				}
				return false;
			}

			if (AllowExponent) {
				FindExponent(ref pos, s);
			}
			if (AllowTrailingSign && !foundSign) {
				// Sign + Currency
				FindSign(ref pos, s, nfi, ref foundSign, ref negative);
				if (foundSign) {
					if (AllowTrailingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
						return false;
					}
					if (AllowCurrencySymbol) {
						FindCurrency(ref pos, s, nfi, ref foundCurrency);
					}
				}
			}

			if (AllowCurrencySymbol && !foundCurrency) {
				// Currency + sign
				FindCurrency(ref pos, s, nfi, ref foundCurrency);
				if (foundCurrency) {
					if (AllowTrailingWhite && !JumpOverWhite(ref pos, s, true, tryParse, ref exc)) {
						return false;
					}
					if (!foundSign && AllowTrailingSign) {
						FindSign(ref pos, s, nfi, ref foundSign, ref negative);
					}
				}
			}

			if (AllowTrailingWhite && pos < s.Length && !JumpOverWhite(ref pos, s, false, tryParse, ref exc)) {
				return false;
			}

			if (foundOpenParentheses) {
				if (pos >= s.Length || s[pos++] != ')') {
					if (!tryParse) {
						exc = GetFormatException();
					}
					return false;
				}
				if (AllowTrailingWhite && pos < s.Length && !JumpOverWhite(ref pos, s, false, tryParse, ref exc)) {
					return false;
				}
			}

			if (pos < s.Length && s[pos] != '\u0000') {
				if (!tryParse) {
					exc = GetFormatException();
				}
				return false;
			}

			if (!negative && !AllowHexSpecifier) {
				number = checked(-number);
			}

			result = number;

			return true;
		}

	}
}

#endif