//
// Conversion.cs
//
// Author:
// Chris J Breisch (cjbreisch@altavista.net)
//
// (C) 2002 Chris J Breisch
//
using System;
using System.Text.RegularExpressions;
namespace Microsoft.VisualBasic {
[Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute]
sealed public class Conversion {
//
//
// Collection : The BASIC Collection Object
//
//
//
//
// Declarations
private static readonly char[] _HexDigits = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
private static readonly char[] _OctDigits = {
'0', '1', '2', '3', '4', '5', '6', '7'
};
private static readonly long[] _Maxes = {
32767, 2147483647, 9223372036854775807
};
private enum SizeIndexes {
Int16 = 0,
Int32 = 1,
Int64 = 2
};
// Constructors
// Properties
// Methods
[MonoTODO]
public static string ErrorToString () {
throw new NotImplementedException ();
}
[MonoTODO]
public static string ErrorToString (
System.Int32 ErrorNumber) {
throw new NotImplementedException ();
}
// Return whether d is +/- Could do this with a macro,
// but this is cleaner
private static int Sign(double d) { return d > 0 ? 1 : -1;}
// try to cast an Object to a string...used in several places
private static string CastToString (System.Object Expression) {
try {
return (string)Expression;
}
catch {
throw new InvalidCastException();
}
}
// Fix on Integer types doesn't do anything
public static short Fix (short Number) { return Number; }
public static int Fix (int Number) { return Number; }
public static long Fix (long Number) { return Number; }
// Fix on other numberic types = Sign(Number) * Int(Abs(Number))
public static double Fix (double Number) {
return Sign(Number) * Int (Math.Abs (Number));
}
public static float Fix (float Number) {
return Sign(Number) * Int (Math.Abs (Number));
}
public static decimal Fix (decimal Number) {
return Sign((double)Number) * Int (Math.Abs (Number));
}
// Fix on an Object type is trickier
// first we have to cast it to the right type
public static System.Object Fix (System.Object Number)
{
// always start out by throwing an exception
// if Number is null
if (Number == null) {
throw new ArgumentNullException ("Number",
"Value cannot be null");
}
TypeCode TC = Type.GetTypeCode (Number.GetType ());
// switch on TypeCode and call appropriate Fix method
switch (TC) {
case TypeCode.Decimal:
return Fix (Convert.ToDecimal (Number));
case TypeCode.Double:
return Fix (Convert.ToDouble (Number));
case TypeCode.Single:
return Fix (Convert.ToSingle (Number));
case TypeCode.String:
return Fix (Double.Parse (
CastToString (Number)));
// for integer types, don't need to do anything
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return Number;
// spec defines Empty as returning 0
case TypeCode.Empty:
return 0;
// we can't convert these types
case TypeCode.Boolean:
case TypeCode.Char:
case TypeCode.DateTime:
case TypeCode.DBNull:
case TypeCode.Object:
default:
throw new ArgumentException (
"Type of argument 'Number' is '"
+ Number.GetType().FullName +
"', which is not numeric.");
}
}
// Int on Integer types doesn't do anything
public static short Int (short Number) { return Number; }
public static int Int (int Number) { return Number; }
public static long Int (long Number) { return Number; }
// Int on other numberic types is same thing as "Floor"
public static double Int (double Number) {
return (double) Math.Floor(Number);
}
public static float Int (float Number) {
return (float) Math.Floor(Number);
}
public static decimal Int (decimal Number) {
return Decimal.Floor(Number);
}
// doing an Int on an Object is trickier
// first we have to cast to the correct type
public static System.Object Int (System.Object Number) {
// always start out by throwing an exception
// if Number is null
if (Number == null) {
throw new ArgumentNullException("Number",
"Value cannot be null");
}
TypeCode TC = Type.GetTypeCode (Number.GetType ());
// switch on TypeCode and call appropriate Int method
switch (TC) {
case TypeCode.Decimal:
return Int (Convert.ToDecimal (Number));
case TypeCode.Double:
return Int (Convert.ToDouble (Number));
case TypeCode.Single:
return Int (Convert.ToSingle (Number));
case TypeCode.String:
return Int (Double.Parse (
CastToString(Number)));
// Int on integer types does nothing
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.SByte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return Number;
// Spec defines Empty as returning 0
case TypeCode.Empty:
return 0;
// otherwise, it's we can't cast to a numeric
case TypeCode.Boolean:
case TypeCode.Char:
case TypeCode.DateTime:
case TypeCode.DBNull:
case TypeCode.Object:
default:
throw new ArgumentException (
"Type of argument 'Number' is '" +
Number.GetType().FullName +
"', which is not numeric.");
}
}
// we use this internally to get a string
// representation of a number in a specific base
private static string ToBase (ulong Number, int Length,
char[] BaseDigits, uint Base) {
int i;
ulong r;
// we use a char array here for performance
char [] c = new Char[Length];
string s = null;
for (i = Length - 1; i >= 0; i--) {
r = Number % Base;
Number = Number / Base;
c[i] = BaseDigits[r];
if (Number == 0) {
s = new string (c, i, Length - i);
break;
}
}
if (s == null) {
return new string (c);
}
else {
return s;
}
}
// convert a number to Hex
// a little bit of magic goes on here with negative #'s
private static string ToHex(long Number, int Digits,
SizeIndexes Size) {
ulong UNumber;
if (Number < 0) {
// we add maxint of the Number's type
// twice and then 2 more...this has the
// effect of turning it into a ulong
// that has the same hex representation
UNumber = (ulong)((Number + 2) +
_Maxes[(int)Size]) +
(ulong)_Maxes[(int)Size];
}
else {
UNumber = (ulong)Number;
}
return ToBase(UNumber, Digits, _HexDigits, 16);
}
// call our private function,
// passing it the size of the item to convert
public static string Hex (short Number) {
return ToHex(Number, 4, SizeIndexes.Int16);
}
public static string Hex (byte Number) {
return ToHex(Number, 2, SizeIndexes.Int16);
}
public static string Hex (int Number) {
return ToHex(Number, 8, SizeIndexes.Int32);
}
public static string Hex (long Number) {
return ToHex(Number, 16, SizeIndexes.Int64);
}
// Objects are trickier
// first we have to cast to appropriate type
public static System.String Hex (System.Object Number) {
// always start out by throwing an exception
// if Number is null
if (Number == null) {
throw new ArgumentNullException ("Number",
"Value cannot be null");
}
TypeCode TC = Type.GetTypeCode (Number.GetType ());
switch (TC) {
// try to parse the string as an Int32,
// then an Int64, if that fails
case TypeCode.String:
try {
return Hex (
Int32.Parse (
CastToString (Number)));
}
catch {
return Hex (
Int64.Parse (
CastToString (Number)));
}
// for the int types,
// just call the normal "Hex" for that type
case TypeCode.Byte:
return Hex ((byte)Number);
case TypeCode.Int16:
return Hex ((short)Number);
case TypeCode.Int32:
return Hex ((int)Number);
case TypeCode.Int64:
return Hex ((long)Number);
// empty is defined as returning 0
case TypeCode.Empty:
return "0";
// we can't do any of these types
case TypeCode.Boolean:
case TypeCode.Char:
case TypeCode.DBNull:
case TypeCode.DateTime:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Object:
case TypeCode.SByte:
case TypeCode.Single:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
default:
throw new ArgumentException (
"Type of argument 'Number' is '" +
Number.GetType().FullName +
"', which is not numeric.");
}
}
// ToOct works just like ToHex, only in Octal.
private static string ToOct(long Number, int Digits,
SizeIndexes Size) {
ulong UNumber;
if (Number < 0) {
// for neg numbers add the maxint of
// the appropriate size twice, and then two more
// this has the effect of turning it
// into a ulong with the same oct representation
UNumber = (ulong)((Number + 2) +
_Maxes[(int)Size]) +
(ulong)(_Maxes[(int)Size]);
}
else {
UNumber = (ulong)Number;
}
return ToBase (UNumber, Digits, _OctDigits, 8);
}
// call ToOct with appropriate information
public static string Oct (short Number) {
return ToOct(Number, 6, SizeIndexes.Int16);
}
public static string Oct (byte Number) {
return ToOct(Number, 3, SizeIndexes.Int16);
}
public static string Oct (int Number) {
return ToOct(Number, 11, SizeIndexes.Int32);
}
public static string Oct (long Number) {
return ToOct(Number, 22, SizeIndexes.Int64);
}
// Objects are always trickier
// first need to cast to appropriate type
public static string Oct (System.Object Number) {
// first, always throw an exception if Number is null
if (Number == null) {
throw new ArgumentNullException("Number",
"Value cannot be null");
}
TypeCode TC = Type.GetTypeCode (Number.GetType ());
switch (TC) {
// try to parse a string as an Int32
// and then an Int64
case TypeCode.String:
try {
return Oct (
Int32.Parse (
CastToString (Number)));
}
catch {
return Oct (
Int64.Parse (
CastToString (Number)));
}
// integer types just call the appropriate "Oct"
case TypeCode.Byte:
return Oct ((byte)Number);
case TypeCode.Int16:
return Oct ((short)Number);
case TypeCode.Int32:
return Oct ((int)Number);
case TypeCode.Int64:
return Oct ((long)Number);
// Empty is defined as returning 0
case TypeCode.Empty:
return "0";
// We can't convert these to Octal
case TypeCode.Boolean:
case TypeCode.Char:
case TypeCode.DBNull:
case TypeCode.DateTime:
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Object:
case TypeCode.SByte:
case TypeCode.Single:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
default:
throw new ArgumentException (
"Type of argument 'Number' is '" +
Number.GetType().FullName +
"', which is not numeric.");
}
}
// Str is pretty easy now that we have a language
// with a ToString method()
public static string Str (System.Object Number) {
// check for null as always and throw an exception
if (Number == null) {
throw new ArgumentNullException("Number");
}
switch (Type.GetTypeCode (Number.GetType ())) {
// for unsigned types, just call ToString
case TypeCode.Byte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return Number.ToString();
// for signed types, we have to leave a
// space for the missing + sign
case TypeCode.Decimal:
return ((decimal)Number > 0 ? " " : "")
+ Number.ToString();
case TypeCode.Double:
return ((double)Number > 0 ? " " : "")
+ Number.ToString();
case TypeCode.Int16:
return ((short)Number > 0 ? " " : "")
+ Number.ToString();
case TypeCode.Int32:
return ((int)Number > 0 ? " " : "")
+ Number.ToString();
case TypeCode.Int64:
return ((long)Number > 0 ? " " : "")
+ Number.ToString();
case TypeCode.SByte:
return ((sbyte)Number > 0 ? " " : "")
+ Number.ToString();
case TypeCode.Single:
return ((float)Number > 0 ? " " : "")
+ Number.ToString();
// can't cast anything else to a Number
default:
throw new InvalidCastException(
"Argument 'Number' cannot be converted to a numeric value.");
}
}
// The Val function is pretty bizarre
// Val ("&HFF") = 255
// Val ("&O377") = 255
// Val ("1234 Any Street") = 1234
// Val (" 12 45 . 90 7 E + 0 0 2 ") = 1245.907e+002 = 124590.7
public static double Val (string InputStr) {
int i;
int Base;
int NumChars = 0;
char c;
int Length = InputStr.Length;
char[] Number = new char[Length];
bool FoundRadixPrefix = false;
Regex NumberReg;
Match NumberMatch;
Base = 10;
Number[0] = '\0';
// go through string
for (i = 0; i < Length; i++) {
c = InputStr[i];
// look for Radix prefix "&"
if (i == 0 && c == '&') {
FoundRadixPrefix = true;
}
// look for an H or O following the prefix
else if (FoundRadixPrefix && i == 1 &&
(char.ToLower(c) == 'h' ||
char.ToLower(c) == 'o')) {
if (c == 'H') {
Base = 16;
}
else {
Base = 8;
}
}
// if we didn't find a radix prefix,
// ignore whitespace
else if (char.IsWhiteSpace(c) && (Base == 10)) {
continue;
}
// mash what's left together
else {
Number[NumChars++] = c;
}
}
// now we have a string to parse
switch (Base) {
// FIXME : for Octal and Hex,
// Regex is probably overkill
// Even for base 10, it might be faster
// to write our own parser
case 8:
NumberReg = new Regex ("^[0-7]*");
NumberMatch = NumberReg.Match (
new string(Number, 0, NumChars));
break;
case 16:
NumberReg = new Regex ("^[0-9a-f]*",
RegexOptions.IgnoreCase);
NumberMatch = NumberReg.Match (
new string(Number, 0, NumChars));
break;
case 10:
default:
NumberReg = new Regex (
"^[+-]?\\d*\\.?\\d*(e?[+-]?\\d*)",
RegexOptions.IgnoreCase);
NumberMatch = NumberReg.Match (
new string(Number, 0, NumChars));
break;
}
// we found a match, try to convert it
if (NumberMatch.Success) {
try {
switch (Base) {
case 10:
return
Convert.ToDouble (
NumberMatch.Value);
case 8:
case 16:
return (double)
Convert.ToInt64 (
NumberMatch.Value,
Base);
default:
return (double)0;
}
}
catch {
throw new OverflowException();
}
}
else {
return (double)0;
}
}
// Val on a char type is pretty simple '9' = 9, 'a' = exception
public static int Val (char Expression) {
if (char.IsDigit(Expression)) {
return Expression - '0';
}
else {
throw new ArgumentException();
}
}
// if it's an object, and we can't convert
// it to a string, it's an exception
public static double Val (System.Object Expression) {
// always check for null first
if (Expression == null) {
throw new ArgumentNullException ("Expression",
"Value cannot be null");
}
try {
return Val (CastToString (Expression));
}
catch {
throw new ArgumentException(
"Type of argument 'Expression' is '" +
Expression.GetType().FullName +
"', which can nt be converted to numeric.");
}
}
// Events
}
}