Port remaining members of Microsoft.VisualBasic.Strings (dotnet/corefx#37123)
authorCharles Stoner <chucks@microsoft.com>
Wed, 24 Apr 2019 01:11:11 +0000 (18:11 -0700)
committerGitHub <noreply@github.com>
Wed, 24 Apr 2019 01:11:11 +0000 (18:11 -0700)
Commit migrated from https://github.com/dotnet/corefx/commit/48cc70c649b7876ca43155451cafba5499107c07

src/libraries/Microsoft.VisualBasic.Core/ref/Microsoft.VisualBasic.Core.cs
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft.VisualBasic.Core.vbproj
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ObjectType.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Constants.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Globals.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Helpers/UnsafeNativeMethods.vb
src/libraries/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Strings.vb
src/libraries/Microsoft.VisualBasic.Core/tests/StringsTests.cs

index 5d0293da820bfe9839653d49242d9b122aa489a2..400868539eccd29b8e4764a51fe9d9de84cd7208 100644 (file)
@@ -69,6 +69,7 @@ namespace Microsoft.VisualBasic
         public const Microsoft.VisualBasic.CompareMethod vbBinaryCompare = Microsoft.VisualBasic.CompareMethod.Binary;
         public const string vbCr = "\r";
         public const string vbCrLf = "\r\n";
+        public const TriState vbFalse = TriState.False;
         public const string vbFormFeed = "\f";
         public const string vbLf = "\n";
         [System.ObsoleteAttribute("For a carriage return and line feed, use vbCrLf.  For the current platform's newline, use System.Environment.NewLine.")]
@@ -77,6 +78,8 @@ namespace Microsoft.VisualBasic
         public const string vbNullString = null;
         public const string vbTab = "\t";
         public const Microsoft.VisualBasic.CompareMethod vbTextCompare = Microsoft.VisualBasic.CompareMethod.Text;
+        public const TriState vbTrue = TriState.True;
+        public const TriState vbUseDefault = TriState.UseDefault;
         public const string vbVerticalTab = "\v";
     }
     public sealed partial class ControlChars
@@ -153,6 +156,14 @@ namespace Microsoft.VisualBasic
         public static System.DateTime Now { get { throw null; } }
         public static System.DateTime Today { get { throw null; } }
     }
+    public enum DateFormat
+    {
+        GeneralDate = 0,
+        LongDate = 1,
+        LongTime = 3,
+        ShortDate = 2,
+        ShortTime = 4,
+    }
     public sealed partial class ErrObject
     {
         internal ErrObject() { }
@@ -219,9 +230,19 @@ namespace Microsoft.VisualBasic
         public static char ChrW(int CharCode) { throw null; }
         public static string[] Filter(object[] Source, string Match, bool Include = true, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute]Microsoft.VisualBasic.CompareMethod Compare = Microsoft.VisualBasic.CompareMethod.Binary) { throw null; }
         public static string[] Filter(string[] Source, string Match, bool Include = true, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute]Microsoft.VisualBasic.CompareMethod Compare = Microsoft.VisualBasic.CompareMethod.Binary) { throw null; }
+        public static string Format(object Expression, string Style = "") { throw null; }
+        public static string FormatCurrency(object Expression, int NumDigitsAfterDecimal = -1, TriState IncludeLeadingDigit = TriState.UseDefault, TriState UseParensForNegativeNumbers = TriState.UseDefault, TriState GroupDigits = TriState.UseDefault) { throw null; }
+        public static string FormatDateTime(System.DateTime Expression, DateFormat NamedFormat = DateFormat.GeneralDate) { throw null; }
+        public static string FormatNumber(object Expression, int NumDigitsAfterDecimal = -1, TriState IncludeLeadingDigit = TriState.UseDefault, TriState UseParensForNegativeNumbers = TriState.UseDefault, TriState GroupDigits = TriState.UseDefault) { throw null; }
+        public static string FormatPercent(object Expression, int NumDigitsAfterDecimal = -1, TriState IncludeLeadingDigit = TriState.UseDefault, TriState UseParensForNegativeNumbers = TriState.UseDefault, TriState GroupDigits = TriState.UseDefault) { throw null; }
+        public static char GetChar(string str, int Index) { throw null; }
         public static int InStr(int StartPos, string String1, string String2, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute]Microsoft.VisualBasic.CompareMethod Compare = Microsoft.VisualBasic.CompareMethod.Binary) { throw null; }
         public static int InStr(string String1, string String2, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute]Microsoft.VisualBasic.CompareMethod Compare = Microsoft.VisualBasic.CompareMethod.Binary) { throw null; }
         public static int InStrRev(string StringCheck, string StringMatch, int Start = -1, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute]Microsoft.VisualBasic.CompareMethod Compare = Microsoft.VisualBasic.CompareMethod.Binary) { throw null; }
+        public static string Join(object[] SourceArray, string Delimiter = " ") { throw null; }
+        public static string Join(string[] SourceArray, string Delimiter = " ") { throw null; }
+        public static char LCase(char Value) { throw null; }
+        public static string LCase(string Value) { throw null; }
         public static string Left(string str, int Length) { throw null; }
         public static int Len(bool Expression) { throw null; }
         public static int Len(byte Expression) { throw null; }
@@ -243,14 +264,31 @@ namespace Microsoft.VisualBasic
         public static int Len(uint Expression) { throw null; }
         [System.CLSCompliantAttribute(false)]
         public static int Len(ulong Expression) { throw null; }
+        public static string LSet(string Source, int Length) { throw null; }
         public static string LTrim(string str) { throw null; }
         public static string Mid(string str, int Start) { throw null; }
         public static string Mid(string str, int Start, int Length) { throw null; }
+        public static string Replace(string Expression, string Find, string Replacement, int Start = 1, int Count = -1, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute] CompareMethod Compare = CompareMethod.Binary) { throw null; }
         public static string Right(string str, int Length) { throw null; }
+        public static string RSet(string Source, int Length) { throw null; }
         public static string RTrim(string str) { throw null; }
+        public static string Space(int Number) { throw null; }
+        public static string[] Split(string Expression, string Delimiter = " ", int Limit = -1, [Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute] CompareMethod Compare = CompareMethod.Binary) { throw null; }
         public static int StrComp(string String1, string String2, CompareMethod Compare = CompareMethod.Binary) { throw null; }
+        public static string StrConv(string str, Microsoft.VisualBasic.VbStrConv Conversion, int LocaleID = 0) { throw null; }
+        public static string StrDup(int Number, char Character) { throw null; }
+        public static object StrDup(int Number, object Character) { throw null; }
+        public static string StrDup(int Number, string Character) { throw null; }
         public static string StrReverse(string Expression) { throw null; }
         public static string Trim(string str) { throw null; }
+        public static char UCase(char Value) { throw null; }
+        public static string UCase(string Value) { throw null; }
+    }
+    public enum TriState
+    {
+        False = 0,
+        True = -1,
+        UseDefault = -2,
     }
     public enum VariantType
     {
@@ -298,6 +336,21 @@ namespace Microsoft.VisualBasic
         public static float Rnd() { throw null; }
         public static float Rnd(float Number) { throw null; }
     }
+    [System.FlagsAttribute]
+    public enum VbStrConv
+    {
+        Hiragana = 32,
+        Katakana = 16,
+        LinguisticCasing = 1024,
+        Lowercase = 2,
+        Narrow = 8,
+        None = 0,
+        ProperCase = 3,
+        SimplifiedChinese = 256,
+        TraditionalChinese = 512,
+        Uppercase = 1,
+        Wide = 4,
+    }
 }
 namespace Microsoft.VisualBasic.ApplicationServices
 {
index 926d04c5bc9e44078f9f7be438d53983b6177969..175eaa807c6a9c7824cf835fd7c62fd6cd16fd6a 100644 (file)
@@ -10,6 +10,7 @@
     <OptionCompare>Binary</OptionCompare>
     <WarningsNotAsErrors>42025</WarningsNotAsErrors>
     <DefineConstants>$(DefineConstants),LATEBINDING=True</DefineConstants>
+    <DefineConstants Condition="'$(TargetsWindows)' == 'true'">$(DefineConstants),PLATFORM_WINDOWS=True</DefineConstants>
     <AssemblyName>Microsoft.VisualBasic.Core</AssemblyName>
     <NoStdLib>true</NoStdLib>
     <RemoveIntegerChecks>false</RemoveIntegerChecks>
index f13747083f2d3a746966a0f0e066b5238604b0c0..2eaf92ab4d62ebf497741b22de3fa6093f46761a 100644 (file)
@@ -411,7 +411,7 @@ Namespace Microsoft.VisualBasic.CompilerServices
             ElseIf obj2 Is Nothing Then
                 Return type1
             Else
-                ' An ugly hack. If we do x + y and one of them is DBNull and one of them is String,
+                ' If we do x + y and one of them is DBNull and one of them is String,
                 ' then we convert DBNull to "" and do concatenation. We communicate this by passing
                 ' back TypeCode.DBNull
                 If IsAdd AndAlso
index ff16136d4ce84d71489eaaae66937606eb82daa1..13cbcbf152bdbadedd3109af04dc51cefa8061d3 100644 (file)
@@ -20,5 +20,11 @@ Namespace Global.Microsoft.VisualBasic
         'vbCompareMethod enum values
         Public Const vbBinaryCompare As CompareMethod = CompareMethod.Binary
         Public Const vbTextCompare As CompareMethod = CompareMethod.Text
+
+        'vbTriState
+        Public Const vbUseDefault As TriState = TriState.UseDefault
+        Public Const vbTrue As TriState = TriState.True
+        Public Const vbFalse As TriState = TriState.False
+
     End Module
 End Namespace
index 4a849111ae4d471b65b3693b4d4c4c2522e0c0d4..506e7f8db96adcc9f2a8e269ae97b2454c31959d 100644 (file)
@@ -2,10 +2,43 @@
 ' The .NET Foundation licenses this file to you under the MIT license.
 ' See the LICENSE file in the project root for more information.
 
+Imports System
+
 Namespace Global.Microsoft.VisualBasic
+
     Public Enum CompareMethod
         [Binary] = 0
         [Text] = 1
     End Enum
 
+    Public Enum DateFormat
+        GeneralDate = 0
+        LongDate = 1
+        ShortDate = 2
+        LongTime = 3
+        ShortTime = 4
+    End Enum
+
+    <Flags()> Public Enum VbStrConv
+        [None] = 0
+        [Uppercase] = 1
+        [Lowercase] = 2
+        [ProperCase] = 3
+        [Wide] = 4
+        [Narrow] = 8
+        [Katakana] = 16
+        [Hiragana] = 32
+        '[Unicode]      = 64 'OBSOLETE
+        '[FromUnicode]   = 128 'OBSOLETE
+        [SimplifiedChinese] = 256
+        [TraditionalChinese] = 512
+        [LinguisticCasing] = 1024
+    End Enum
+
+    Public Enum TriState
+        [False] = 0
+        [True] = -1
+        [UseDefault] = -2
+    End Enum
+
 End Namespace
index 41b8b59d8ef0ce7e1d0a1bc1bccb76326b9339f5..2717363a7320f2d2a7ff4150a7e7e5b5744522fa 100644 (file)
@@ -4,11 +4,25 @@
 
 Imports System
 Imports System.Runtime.InteropServices
+Imports System.Runtime.Versioning
 
 Namespace Microsoft.VisualBasic.CompilerServices
 
     <ComVisible(False)>
     Friend NotInheritable Class UnsafeNativeMethods
+
+        <ResourceExposure(ResourceScope.Machine)>
+        <PreserveSig()>
+        Friend Declare Ansi Function LCMapStringA _
+                Lib "kernel32" Alias "LCMapStringA" (ByVal Locale As Integer, ByVal dwMapFlags As Integer,
+                    <MarshalAs(UnmanagedType.LPArray)> ByVal lpSrcStr As Byte(), ByVal cchSrc As Integer, <MarshalAs(UnmanagedType.LPArray)> ByVal lpDestStr As Byte(), ByVal cchDest As Integer) As Integer
+
+        <ResourceExposure(ResourceScope.Machine)>
+        <PreserveSig()>
+        Friend Declare Auto Function LCMapString _
+                Lib "kernel32" (ByVal Locale As Integer, ByVal dwMapFlags As Integer,
+                    ByVal lpSrcStr As String, ByVal cchSrc As Integer, ByVal lpDestStr As String, ByVal cchDest As Integer) As Integer
+
         ''' <summary>
         ''' Frees memory allocated from the local heap. i.e. frees memory allocated
         ''' by LocalAlloc or LocalReAlloc.n
index fb50cc1db872aec7d8a0fe4a8accccf4301f943d..b4ad704c51bc0b4a8f8f1a95a7033718ba03d5ff 100644 (file)
@@ -4,6 +4,7 @@
 
 Imports System
 Imports System.Globalization
+Imports System.Runtime.Versioning
 Imports System.Text
 Imports Microsoft.VisualBasic.CompilerServices
 Imports Microsoft.VisualBasic.CompilerServices.ExceptionUtils
@@ -11,13 +12,148 @@ Imports Microsoft.VisualBasic.CompilerServices.Utils
 
 Namespace Global.Microsoft.VisualBasic
 
-    Public Module Strings
+    Friend NotInheritable Class FormatInfoHolder
+        Implements IFormatProvider
+
+        Friend Sub New(ByVal nfi As NumberFormatInfo)
+            MyBase.New()
+            Me.nfi = nfi
+        End Sub
+
+        Private nfi As NumberFormatInfo
+
+        Private Function GetFormat(ByVal service As Type) As Object Implements IFormatProvider.GetFormat
+            If service Is GetType(NumberFormatInfo) Then
+                Return nfi
+            End If
+            Throw New ArgumentException(GetResourceString(SR.InternalError_VisualBasicRuntime))
+        End Function
 
+    End Class
+
+    Public Module Strings
+        'Positive format strings
+        '0      $n  
+        '1      n$ 
+        '2      $ n  
+        '3      n $ 
+        Private ReadOnly CurrencyPositiveFormatStrings() As String = {"'$'n", "n'$'", "'$' n", "n '$'"} 'Note, we wrap the $ in the literal symbol to avoid misinterpretation when using the escape character \ as a currency mark
+
+        'The negative currency pattern needs to be selected based 
+        '  on the criteria provided for parens
+        'nfi.CurrencyPositivePattern 
+        'Negative format strings
+        '0      ($n) 
+        '1      -$n  
+        '2      $-n  
+        '3      $n- 
+        '4      (n$) 
+        '5      -n$ 
+        '6      n-$ 
+        '7      n$- 
+        '8      -n $ 
+        '9      -$ n  
+        '10     n $- 
+        '11     $ n- 
+        '12     $- n  
+        '13     n- $ 
+        '14     ($ n) 
+        '15     (n $) 
+        Private ReadOnly CurrencyNegativeFormatStrings() As String =
+            {"('$'n)", "-'$'n", "'$'-n", "'$'n-", "(n'$')", "-n'$'", "n-'$'", "n'$'-",
+              "-n '$'", "-'$' n", "n '$'-", "'$' n-", "'$'- n", "n- '$'", "('$' n)", "(n '$')"} 'Note, we wrap the $ in the literal symbol to avoid misinterpretation when using the escape character \ as a currency mark
+
+        'Value Associated Pattern 
+        '0 (n) 
+        '1 -n  
+        '2 - n  
+        '3 n- 
+        '4 n - 
+        Private ReadOnly NumberNegativeFormatStrings() As String =
+            {"(n)", "-n", "- n", "n-", "n -"}
+
+        Friend Enum FormatType
+            Number = 0
+            Percent = 1
+            [Currency] = 2
+        End Enum
+
+        Private Const CODEPAGE_SIMPLIFIED_CHINESE As Integer = 936
+        Private Const CODEPAGE_TRADITIONAL_CHINESE As Integer = 950
         Private Const STANDARD_COMPARE_FLAGS As CompareOptions =
             CompareOptions.IgnoreCase Or CompareOptions.IgnoreWidth Or CompareOptions.IgnoreKanaType
 
+        Private Const NAMEDFORMAT_FIXED As String = "fixed"
+        Private Const NAMEDFORMAT_YES_NO As String = "yes/no"
+        Private Const NAMEDFORMAT_ON_OFF As String = "on/off"
+        Private Const NAMEDFORMAT_PERCENT As String = "percent"
+        Private Const NAMEDFORMAT_STANDARD As String = "standard"
+        Private Const NAMEDFORMAT_CURRENCY As String = "currency"
+        Private Const NAMEDFORMAT_LONG_TIME As String = "long time"
+        Private Const NAMEDFORMAT_LONG_DATE As String = "long date"
+        Private Const NAMEDFORMAT_SCIENTIFIC As String = "scientific"
+        Private Const NAMEDFORMAT_TRUE_FALSE As String = "true/false"
+        Private Const NAMEDFORMAT_SHORT_TIME As String = "short time"
+        Private Const NAMEDFORMAT_SHORT_DATE As String = "short date"
+        Private Const NAMEDFORMAT_MEDIUM_DATE As String = "medium date"
+        Private Const NAMEDFORMAT_MEDIUM_TIME As String = "medium time"
+        Private Const NAMEDFORMAT_GENERAL_DATE As String = "general date"
+        Private Const NAMEDFORMAT_GENERAL_NUMBER As String = "general number"
+
         Friend ReadOnly m_InvariantCompareInfo As CompareInfo = CultureInfo.InvariantCulture.CompareInfo
 
+        'This is shared across Cached
+        Private m_SyncObject As Object = New Object
+        Private m_LastUsedYesNoCulture As CultureInfo
+        Private m_CachedYesNoFormatStyle As String
+
+        Private ReadOnly Property CachedYesNoFormatStyle() As String
+            Get
+                Dim ci As CultureInfo = GetCultureInfo()
+                SyncLock m_SyncObject
+                    If Not m_LastUsedYesNoCulture Is ci Then
+                        m_LastUsedYesNoCulture = ci
+                        m_CachedYesNoFormatStyle = GetResourceString(SR.YesNoFormatStyle)
+                    End If
+                    Return m_CachedYesNoFormatStyle
+                End SyncLock
+            End Get
+        End Property
+
+        Private m_LastUsedOnOffCulture As CultureInfo
+        Private m_CachedOnOffFormatStyle As String
+        Private ReadOnly Property CachedOnOffFormatStyle() As String
+            Get
+                Dim ci As CultureInfo = GetCultureInfo()
+                SyncLock m_SyncObject
+                    If Not m_LastUsedOnOffCulture Is ci Then
+                        m_LastUsedOnOffCulture = ci
+                        m_CachedOnOffFormatStyle = GetResourceString(SR.OnOffFormatStyle)
+                    End If
+                    Return m_CachedOnOffFormatStyle
+                End SyncLock
+            End Get
+        End Property
+
+        Private m_LastUsedTrueFalseCulture As CultureInfo
+        Private m_CachedTrueFalseFormatStyle As String
+        Private ReadOnly Property CachedTrueFalseFormatStyle() As String
+            Get
+                Dim ci As CultureInfo = GetCultureInfo()
+                SyncLock m_SyncObject
+                    If Not m_LastUsedTrueFalseCulture Is ci Then
+                        m_LastUsedTrueFalseCulture = ci
+                        m_CachedTrueFalseFormatStyle = GetResourceString(SR.TrueFalseFormatStyle)
+                    End If
+                    Return m_CachedTrueFalseFormatStyle
+                End SyncLock
+            End Get
+        End Property
+
+        Private Function PRIMARYLANGID(ByVal lcid As Integer) As Integer
+            Return (lcid And &H3FF)
+        End Function
+
         '============================================================================
         ' Character manipulation functions.
         '============================================================================
@@ -336,6 +472,65 @@ EmptyMatchString:
             End Try
         End Function
 
+        Public Function Join(ByVal SourceArray() As Object, Optional ByVal Delimiter As String = " ") As String
+            Dim Size As Integer = UBound(SourceArray)
+            Dim StringSource(Size) As String
+            Dim i As Integer
+
+            Try
+                For i = 0 To Size
+                    StringSource(i) = CStr(SourceArray(i))
+                Next i
+            Catch ex As StackOverflowException
+                Throw ex
+            Catch ex As OutOfMemoryException
+                Throw ex
+            Catch ex As System.Threading.ThreadAbortException
+                Throw ex
+            Catch
+                Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValueType2, "SourceArray", "String"))
+            End Try
+
+            Return Join(StringSource, Delimiter)
+        End Function
+
+        Public Function Join(ByVal SourceArray() As String, Optional ByVal Delimiter As String = " ") As String
+            Try
+                If IsArrayEmpty(SourceArray) Then
+                    'EmptyArray returns empty string
+                    Return Nothing
+                End If
+
+                If SourceArray.Rank <> 1 Then
+                    Throw New ArgumentException(GetResourceString(SR.Argument_RankEQOne1))
+                End If
+
+                Return System.String.Join(Delimiter, SourceArray)
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Public Function LCase(ByVal Value As String) As String
+            Try
+                If Value Is Nothing Then
+                    Return Nothing
+                Else
+                    Return Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToLower(Value)
+                End If
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Public Function LCase(ByVal Value As Char) As Char
+            Try
+                Return Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToLower(Value)
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
         Public Function Len(ByVal Expression As Boolean) As Integer
             Return 2
         End Function
@@ -463,10 +658,348 @@ EmptyMatchString:
             Throw VbMakeException(vbErrors.TypeMismatch)
         End Function
 
+        Public Function Replace(ByVal Expression As String, ByVal Find As String, ByVal Replacement As String, Optional ByVal Start As Integer = 1, Optional ByVal Count As Integer = -1, <Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute()> Optional ByVal [Compare] As CompareMethod = CompareMethod.Binary) As String
+            Try
+                'Validate Parameters
+                If Count < -1 Then
+                    Throw New ArgumentException(GetResourceString(SR.Argument_GEMinusOne1, "Count"))
+                End If
+
+                If Start <= 0 Then
+                    Throw New ArgumentException(GetResourceString("Argument_GTZero1", "Start"))
+                End If
+
+                If (Expression Is Nothing) OrElse (Start > Expression.Length) Then
+                    Return Nothing
+                End If
+
+                If Start <> 1 Then
+                    Expression = Expression.Substring(Start - 1)
+                End If
+
+                If Find Is Nothing Then
+                    GoTo EmptyFindString
+                End If
+
+                If Find.Length = 0 OrElse Count = 0 Then
+EmptyFindString:
+                    Return Expression
+                End If
+
+                If Count = -1 Then
+                    Count = Expression.Length
+                End If
+
+                Return ReplaceInternal(Expression, Find, Replacement, Count, [Compare])
+
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Private Function ReplaceInternal(ByVal Expression As String, ByVal Find As String, ByVal Replacement As String, ByVal Count As Integer, ByVal [Compare] As CompareMethod) As String
+
+            System.Diagnostics.Debug.Assert(Expression <> "", "Expression is empty")
+            System.Diagnostics.Debug.Assert(Find <> "", "Find is empty")
+            System.Diagnostics.Debug.Assert(Count > 0, "Number of replacements is 0 or less")
+            System.Diagnostics.Debug.Assert([Compare] = CompareMethod.Text Or [Compare] = CompareMethod.Binary, "Unknown compare.")
+
+            Dim ExpressionLength As Integer = Expression.Length
+            Dim FindLength As Integer = Find.Length
+
+            Dim Start As Integer
+            Dim FindLocation As Integer
+            Dim Replacements As Integer
+
+            Dim Comparer As CompareInfo
+            Dim CompareFlags As CompareOptions
+
+            Dim Builder As StringBuilder = New StringBuilder(ExpressionLength)
+
+            If [Compare] = CompareMethod.Text Then
+                Comparer = GetCultureInfo().CompareInfo
+                CompareFlags = STANDARD_COMPARE_FLAGS
+            Else
+                Comparer = m_InvariantCompareInfo
+                CompareFlags = CompareOptions.Ordinal
+            End If
+
+            'We build the new string (with the replacements) by walking through Expression, searching for the
+            'Find, and appending sections of Expression or Replacement as we go. For example, if 
+            'Expression = "This is a test.", Find = "is" and Replacement = "YYY" then we would append
+            '"Th" to the new string, then "YYY" then " " then "YYY" and finally " a test."
+            While Start < ExpressionLength
+                If Replacements = Count Then
+                    'We've made all the replacements the caller wanted so append the remaining string
+                    Builder.Append(Expression.Substring(Start))
+                    Exit While
+                End If
+
+                FindLocation = Comparer.IndexOf(Expression, Find, Start, CompareFlags)
+                If FindLocation < 0 Then
+                    'We didn't find the Find string append the rest of the string
+                    Builder.Append(Expression.Substring(Start))
+                    Exit While
+                Else
+                    'Append to our string builder everything up to the found string, then
+                    'append the replacement
+                    Builder.Append(Expression.Substring(Start, FindLocation - Start))
+                    Builder.Append(Replacement)
+                    Replacements += 1
+
+                    'Move the start of our search past the string we just replaced
+                    Start = FindLocation + FindLength
+                End If
+            End While
+
+            Return Builder.ToString()
+
+        End Function
+
+        Public Function Space(ByVal Number As Integer) As String
+
+            If Number >= 0 Then
+                Return New String(ChrW(32), Number)
+            End If
+
+            Throw New ArgumentException(GetResourceString(SR.Argument_GEZero1, "Number"))
+
+        End Function
+
+        Public Function Split(ByVal Expression As String, Optional ByVal Delimiter As String = " ", Optional ByVal Limit As Integer = -1, <Microsoft.VisualBasic.CompilerServices.OptionCompareAttribute()> Optional ByVal [Compare] As CompareMethod = CompareMethod.Binary) As String()
+            Try
+                'Use String.Split
+                Dim aList() As String
+                Dim iDelLen As Integer
+
+                If Expression Is Nothing Then
+                    GoTo EmptyExpression
+                End If
+
+                If Expression.Length = 0 Then
+EmptyExpression:
+                    ReDim aList(0)
+                    aList(0) = ""
+                    Return aList
+                End If
+
+                If Limit = -1 Then
+                    Limit = Expression.Length + 1
+                End If
+
+                If Delimiter Is Nothing Then
+                    iDelLen = 0
+                Else
+                    iDelLen = Delimiter.Length
+                End If
+
+                If iDelLen = 0 Then
+EmptyDelimiterString:
+                    ReDim aList(0)
+                    aList(0) = Expression
+                    Return aList
+                End If
+
+                'Not handled: LIGATURE expansion
+                Return SplitHelper(Expression, Delimiter, Limit, [Compare])
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Private Function SplitHelper(ByVal sSrc As String, ByVal sFind As String, ByVal cMaxSubStrings As Integer, ByVal [Compare] As Integer) As String()
+            Dim cSubStrings As Integer
+            Dim iIndex As Integer
+            Dim iFindLen As Integer
+            Dim iSrcLen As Integer
+            Dim asSubstrings() As String
+            Dim sSubString As String
+            Dim iLastIndex As Integer
+            Dim cDelimPosMax As Integer
+            Dim cmpInfo As CompareInfo
+            Dim flags As CompareOptions
+
+            If sFind Is Nothing Then
+                iFindLen = 0
+            Else
+                iFindLen = sFind.Length
+            End If
+
+            If sSrc Is Nothing Then
+                iSrcLen = 0
+            Else
+                iSrcLen = sSrc.Length
+            End If
+
+            If iFindLen = 0 Then
+                ReDim asSubstrings(0)
+                asSubstrings(0) = sSrc
+                Return asSubstrings
+            End If
+
+            If iSrcLen = 0 Then
+                ReDim asSubstrings(0)
+                asSubstrings(0) = sSrc
+                Return asSubstrings
+            End If
+
+            cDelimPosMax = 20
+
+            If cDelimPosMax > cMaxSubStrings Then
+                cDelimPosMax = cMaxSubStrings
+            End If
+
+            ReDim asSubstrings(cDelimPosMax)
+
+            If [Compare] = CompareMethod.Binary Then
+                flags = CompareOptions.Ordinal
+                cmpInfo = m_InvariantCompareInfo
+            Else
+                cmpInfo = GetCultureInfo().CompareInfo
+                flags = STANDARD_COMPARE_FLAGS
+            End If
+
+            Do While (iLastIndex < iSrcLen)
+                iIndex = cmpInfo.IndexOf(sSrc, sFind, iLastIndex, iSrcLen - iLastIndex, flags)
+
+                If (iIndex = -1) OrElse (cSubStrings + 1 = cMaxSubStrings) Then
+                    'Just put the remainder of the string in the next element
+                    sSubString = sSrc.Substring(iLastIndex)
+                    If sSubString Is Nothing Then
+                        sSubString = ""
+                    End If
+                    asSubstrings(cSubStrings) = sSubString
+                    Exit Do
+                Else
+                    'Put the characters between iLastIndex and iIndex into the next element
+                    sSubString = sSrc.Substring(iLastIndex, iIndex - iLastIndex)
+                    If sSubString Is Nothing Then
+                        sSubString = ""
+                    End If
+                    asSubstrings(cSubStrings) = sSubString
+                    iLastIndex = iIndex + iFindLen
+                End If
+
+                cSubStrings += 1
+
+                If (cSubStrings > cDelimPosMax) Then
+                    cDelimPosMax += 20
+                    If cDelimPosMax > cMaxSubStrings Then
+                        cDelimPosMax = cMaxSubStrings + 1
+                    End If
+                    ReDim Preserve asSubstrings(cDelimPosMax)
+                End If
+
+                'Must Initialize to empty string, otherwise it looks like an object
+                asSubstrings(cSubStrings) = ""
+
+                If cSubStrings = cMaxSubStrings Then
+                    sSubString = sSrc.Substring(iLastIndex)
+                    If sSubString Is Nothing Then
+                        sSubString = ""
+                    End If
+                    asSubstrings(cSubStrings) = sSubString
+                    Exit Do
+                End If
+            Loop
+
+RedimAndExit:
+            If cSubStrings + 1 = asSubstrings.Length Then
+                Return asSubstrings
+            End If
+
+            ReDim Preserve asSubstrings(cSubStrings)
+            Return asSubstrings
+        End Function
+
         '============================================================================
         ' Fixed-length string functions.
         '============================================================================
 
+        Public Function LSet(ByVal Source As String, ByVal Length As Integer) As String
+            If (Length = 0) Then
+                Return ""
+            ElseIf (Source Is Nothing) Then
+                Return New String(" "c, Length)
+            End If
+
+            If Length > Source.Length Then
+                Return Source.PadRight(Length)
+            Else
+                Return Source.Substring(0, Length)
+            End If
+        End Function
+
+        Public Function RSet(ByVal Source As String, ByVal Length As Integer) As String
+            If (Length = 0) Then
+                Return ""
+            ElseIf Source Is Nothing Then
+                Return New String(" "c, Length)
+            End If
+
+            If Length > Source.Length Then
+                Return Source.PadLeft(Length)
+            Else
+                Return Source.Substring(0, Length)
+            End If
+        End Function
+
+        Public Function StrDup(ByVal Number As Integer, ByVal Character As Object) As Object
+            Dim s As String
+            Dim SingleChar As Char
+
+            If Number < 0 Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Number"))
+            End If
+
+            If Character Is Nothing Then
+                Throw New ArgumentNullException(GetResourceString(SR.Argument_InvalidNullValue1, "Character"))
+            End If
+
+            s = TryCast(Character, String)
+
+            If s IsNot Nothing Then
+                If s.Length = 0 Then
+                    Throw New ArgumentException(GetResourceString(SR.Argument_LengthGTZero1, "Character"))
+                End If
+                SingleChar = s.Chars(0)
+            Else
+                Try
+                    SingleChar = CChar(Character)
+                Catch ex As StackOverflowException
+                    Throw ex
+                Catch ex As OutOfMemoryException
+                    Throw ex
+                Catch ex As System.Threading.ThreadAbortException
+                    Throw ex
+                Catch
+                    Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Character"))
+                End Try
+            End If
+
+            Return New String(SingleChar, Number)
+        End Function
+
+        Public Function StrDup(ByVal Number As Integer, ByVal Character As Char) As String
+            If Number < 0 Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_GEZero1, "Number"))
+            End If
+
+            Return New String(Character, Number)
+        End Function
+
+        Public Function StrDup(ByVal Number As Integer, ByVal Character As String) As String
+            If Number < 0 Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_GEZero1, "Number"))
+            End If
+
+            If Character Is Nothing OrElse Character.Length = 0 Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_LengthGTZero1, "Character"))
+            End If
+
+            Return New String(Character.Chars(0), Number)
+        End Function
+
         Public Function StrReverse(ByVal Expression As String) As String
 
             If (Expression Is Nothing) Then
@@ -558,6 +1091,802 @@ EmptyMatchString:
 
         End Function
 
+        Public Function UCase(ByVal [Value] As String) As String
+            Try
+                If Value Is Nothing Then
+                    Return ""
+                Else
+                    Return Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToUpper(Value)
+                End If
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Public Function UCase(ByVal Value As Char) As Char
+            Try
+                Return Threading.Thread.CurrentThread.CurrentCulture.TextInfo.ToUpper(Value)
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        '*************************************************************
+        '** PERF NOTE:
+        '** All Format calls must go through FormatNamed
+        '** But we don't want to put a bunch of overhead on the more
+        '** common cases that are not named formats
+        '** The expensive CompareInfo.Compare calls have been limited
+        '** just one call 
+        '**************************************************************
+
+        Private Function FormatNamed(ByVal Expression As Object, ByVal Style As String, ByRef ReturnValue As String) As Boolean
+            Dim StyleLength As Integer = Style.Length
+
+            ReturnValue = Nothing
+
+            Select Case StyleLength
+
+                Case 5
+                    Select Case Style.Chars(0)
+                        '(F)ixed
+                        Case "f"c, "F"c
+                            If String.Compare(Style, NAMEDFORMAT_FIXED, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDbl(Expression).ToString("0.00", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 6
+                    'switch off 1st char (index 0) to reduce number of string compares
+                    '(Y)es/no
+                    '(O)n/off
+                    Select Case Style.Chars(0)
+                        Case "y"c, "Y"c
+                            If String.Compare(Style, NAMEDFORMAT_YES_NO, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CInt(CBool(Expression)).ToString(CachedYesNoFormatStyle, Nothing)
+                                Return True
+                            End If
+
+                        Case "o"c, "O"c
+                            If String.Compare(Style, NAMEDFORMAT_ON_OFF, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CInt(CBool(Expression)).ToString(CachedOnOffFormatStyle, Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 7
+                    'switch off 1st char (index 0) to reduce number of string compares
+                    '(P)ercent
+                    Select Case Style.Chars(0)
+                        Case "p"c, "P"c
+                            If String.Compare(Style, NAMEDFORMAT_PERCENT, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDbl(Expression).ToString("0.00%", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 8
+                    'switch off 6th char (index 5) to reduce number of string compares
+                    '(S)tandard
+                    '(C)urrency
+
+                    Select Case Style.Chars(0)
+                        Case "s"c, "S"c
+                            If String.Compare(Style, NAMEDFORMAT_STANDARD, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDbl(Expression).ToString("N2", Nothing)
+                                Return True
+                            End If
+                        Case "c"c, "C"c
+                            If String.Compare(Style, NAMEDFORMAT_CURRENCY, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDbl(Expression).ToString("C", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 9
+                    'switch off 6th char (index 5) to reduce number of string compares
+                    'Long (T)ime
+                    'Long (D)ate
+
+                    Select Case Style.Chars(5)
+                        Case "t"c, "T"c
+                            If String.Compare(Style, NAMEDFORMAT_LONG_TIME, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("T", Nothing)
+                                Return True
+                            End If
+
+                        Case "d"c, "D"c
+                            If String.Compare(Style, NAMEDFORMAT_LONG_DATE, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("D", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 10
+                    'switch off 7th char (index 6) to reduce number of string compares
+                    'true/f(A)lse
+                    'short (T)ime
+                    'short (D)ate
+                    'scient(I)fic
+
+                    Select Case Style.Chars(6)
+                        Case "a"c, "A"c
+                            If String.Compare(Style, NAMEDFORMAT_TRUE_FALSE, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CInt(CBool(Expression)).ToString(CachedTrueFalseFormatStyle, Nothing)
+                                Return True
+                            End If
+
+                        Case "t"c, "T"c
+                            If String.Compare(Style, NAMEDFORMAT_SHORT_TIME, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("t", Nothing)
+                                Return True
+                            End If
+
+                        Case "d"c, "D"c
+                            If String.Compare(Style, NAMEDFORMAT_SHORT_DATE, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("d", Nothing)
+                                Return True
+                            End If
+
+                        Case "i"c, "I"c
+                            If String.Compare(Style, NAMEDFORMAT_SCIENTIFIC, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                Dim dbl As Double
+                                dbl = CDbl(Expression)
+                                If System.Double.IsNaN(dbl) OrElse System.Double.IsInfinity(dbl) Then
+                                    ReturnValue = dbl.ToString("G", Nothing)
+                                Else
+                                    ReturnValue = dbl.ToString("0.00E+00", Nothing)
+                                End If
+                                Return True
+                            End If
+
+                    End Select
+
+                Case 11
+                    'switch off 8th char (index 7) to reduce number of string compares
+                    'medium (T)ime
+                    'medium (D)ate
+
+                    Select Case Style.Chars(7)
+                        Case "t"c, "T"c
+                            If String.Compare(Style, NAMEDFORMAT_MEDIUM_TIME, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("T", Nothing)
+                                Return True
+                            End If
+
+                        Case "d"c, "D"c
+                            If String.Compare(Style, NAMEDFORMAT_MEDIUM_DATE, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("D", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 12
+                    Select Case Style.Chars(0)
+                        Case "g"c, "G"c
+                            If String.Compare(Style, NAMEDFORMAT_GENERAL_DATE, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDate(Expression).ToString("G", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+                Case 14
+                    Select Case Style.Chars(0)
+                        Case "g"c, "G"c
+                            If String.Compare(Style, NAMEDFORMAT_GENERAL_NUMBER, StringComparison.OrdinalIgnoreCase) = 0 Then
+                                ReturnValue = CDbl(Expression).ToString("G", Nothing)
+                                Return True
+                            End If
+                    End Select
+
+            End Select
+
+            Return False
+
+        End Function
+
+        '============================================================================
+        ' Format functions.
+        '============================================================================
+        Public Function Format(ByVal Expression As Object, Optional ByVal Style As String = "") As String
+            Try
+                Dim cp As IFormatProvider = Nothing 'GetCultureInfo()
+                Dim tc As TypeCode
+                Dim iformat As IFormattable = Nothing
+
+                If (Expression Is Nothing) OrElse (Expression.GetType() Is Nothing) Then
+                    Return ""
+                End If
+
+                If Style Is Nothing OrElse Style.Length = 0 Then
+                    Return CStr(Expression)
+                End If
+
+                Dim ConvertibleExpression As IConvertible = CType(Expression, IConvertible)
+                tc = ConvertibleExpression.GetTypeCode()
+
+                If Style.Length > 0 Then
+                    Try
+                        Dim ReturnValue As String = Nothing
+
+                        If FormatNamed(Expression, Style, ReturnValue) Then
+                            Return ReturnValue
+                        End If
+                    Catch ex As StackOverflowException
+                        Throw ex
+                    Catch ex As OutOfMemoryException
+                        Throw ex
+                    Catch ex As System.Threading.ThreadAbortException
+                        Throw ex
+                    Catch
+                        'Object could not be converted to required type
+                        'so just return the string
+                        Return CStr(Expression)
+                    End Try
+                End If
+
+                iformat = TryCast(Expression, IFormattable)
+
+                If iformat Is Nothing Then
+                    tc = System.Convert.GetTypeCode(Expression)
+                    If tc <> TypeCode.String AndAlso tc <> TypeCode.Boolean Then
+                        Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Expression"))
+                    End If
+                End If
+
+                Select Case tc
+                    Case TypeCode.Boolean
+                        Return System.String.Format(cp, Style, CStr(ConvertibleExpression.ToBoolean(Nothing)))
+                    Case TypeCode.SByte,
+                         TypeCode.Byte,
+                         TypeCode.Int16,
+                         TypeCode.UInt16,
+                         TypeCode.Int32,
+                         TypeCode.UInt32,
+                         TypeCode.Int64,
+                         TypeCode.UInt64,
+                         TypeCode.Decimal,
+                         TypeCode.DateTime,
+                         TypeCode.Char,
+                         TypeCode.Object
+                        Return iformat.ToString(Style, cp)
+                    Case TypeCode.DBNull
+                        Return ""
+                    Case TypeCode.Double
+                        Dim dbl As Double
+
+                        dbl = ConvertibleExpression.ToDouble(Nothing)
+
+                        If Style Is Nothing OrElse Style.Length = 0 Then
+                            Return CStr(dbl)
+                        End If
+
+                        If dbl = 0 Then
+                            'Used to get rid of possible negative zero, 
+                            'which will format as -0
+                            dbl = 0
+                        End If
+                        Return dbl.ToString(Style, cp)
+                    Case TypeCode.Empty
+                        Return ""
+                    Case TypeCode.Single
+                        Dim sng As Single
+
+                        sng = ConvertibleExpression.ToSingle(Nothing)
+
+                        If Style Is Nothing OrElse Style.Length = 0 Then
+                            Return CStr(sng)
+                        End If
+
+                        If sng = 0 Then
+                            'Used to get rid of possible negative zero
+                            sng = 0
+                        End If
+
+                        Return sng.ToString(Style, cp)
+                    Case TypeCode.String
+                        Return System.String.Format(cp, Style, Expression)
+                    Case Else
+                        Return iformat.ToString(Style, cp)
+                End Select
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Public Function FormatCurrency(ByVal Expression As Object,
+            Optional ByVal NumDigitsAfterDecimal As Integer = -1,
+            Optional ByVal IncludeLeadingDigit As TriState = TriState.UseDefault,
+            Optional ByVal UseParensForNegativeNumbers As TriState = TriState.UseDefault,
+            Optional ByVal GroupDigits As TriState = TriState.UseDefault) As String
+
+            Dim ifmt As IFormattable
+            Dim typ As Type
+            Dim fp As IFormatProvider = Nothing
+            Dim dbl As Double
+
+            Try
+                ValidateTriState(IncludeLeadingDigit)
+                ValidateTriState(UseParensForNegativeNumbers)
+                ValidateTriState(GroupDigits)
+
+                If NumDigitsAfterDecimal > 99 Then  'Was 255 in VB6, but System.Globalization.NumberFormatInfo.CurrencyDecimalDigits limits this to 99.
+                    Throw New ArgumentException(GetResourceString(SR.Argument_Range0to99_1, "NumDigitsAfterDecimal"))
+                End If
+
+                If Expression Is Nothing Then
+                    Return ""
+                End If
+
+                typ = Expression.GetType()
+
+                If typ Is GetType(System.String) Then
+                    Expression = CDbl(Expression)
+                ElseIf Not Symbols.IsNumericType(typ) Then
+                    Throw New InvalidCastException(GetResourceString(SR.InvalidCast_FromTo, VBFriendlyName(typ), "Currency"))
+                End If
+
+                ifmt = CType(Expression, IFormattable)
+
+                Dim FormatStyle As String
+                If IncludeLeadingDigit = TriState.False Then
+                    dbl = CDbl(Expression)
+                    If dbl >= 1 OrElse dbl <= -1 Then
+                        ' If leading digit doesn't matter, this avoids
+                        ' going through the overhead of creating a format string
+                        IncludeLeadingDigit = TriState.True
+                    End If
+                End If
+                FormatStyle = GetCurrencyFormatString(IncludeLeadingDigit, NumDigitsAfterDecimal, UseParensForNegativeNumbers, GroupDigits, fp)
+
+                Return ifmt.ToString(FormatStyle, fp)
+
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Public Function FormatDateTime(ByVal Expression As DateTime, Optional ByVal NamedFormat As DateFormat = DateFormat.GeneralDate) As String
+            Dim sFormat As String
+
+            Try
+                Select Case NamedFormat
+                    Case DateFormat.LongDate
+                        sFormat = "D"
+                    Case DateFormat.ShortDate
+                        sFormat = "d"
+                    Case DateFormat.LongTime
+                        sFormat = "T"
+                    Case DateFormat.ShortTime
+                        sFormat = "HH:mm"
+                    Case DateFormat.GeneralDate
+                        If Expression.TimeOfDay.Ticks = Expression.Ticks Then
+                            'Date is 1/1/0001 - don't print date part
+                            'Same as LongTime
+                            sFormat = "T"
+                        ElseIf Expression.TimeOfDay.Ticks = 0 Then
+                            '12AM - don't print time part
+                            'Same as ShortDate
+                            sFormat = "d"
+                        Else
+                            'Short date + Long Time
+                            sFormat = "G"
+                        End If
+                    Case Else
+                        Throw VbMakeException(vbErrors.IllegalFuncCall)
+                End Select
+
+                Return Expression.ToString(sFormat, Nothing)
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Public Function FormatNumber(ByVal Expression As Object, Optional ByVal NumDigitsAfterDecimal As Integer = -1, Optional ByVal IncludeLeadingDigit As TriState = TriState.UseDefault, Optional ByVal UseParensForNegativeNumbers As TriState = TriState.UseDefault, Optional ByVal GroupDigits As TriState = TriState.UseDefault) As String
+            Dim ifmt As IFormattable
+            Dim typ As Type
+
+            Try
+                ValidateTriState(IncludeLeadingDigit)
+                ValidateTriState(UseParensForNegativeNumbers)
+                ValidateTriState(GroupDigits)
+
+                If Expression Is Nothing Then
+                    Return ""
+                End If
+
+                typ = Expression.GetType()
+
+                If typ Is GetType(System.String) Then
+                    Expression = CDbl(Expression)
+                ElseIf typ Is GetType(System.Boolean) Then
+                    If CBool(Expression) Then
+                        Expression = -1.0
+                    Else
+                        Expression = 0.0
+                    End If
+                ElseIf Not Symbols.IsNumericType(typ) Then
+                    Throw New InvalidCastException(GetResourceString(SR.InvalidCast_FromTo, VBFriendlyName(typ), "Currency"))
+                End If
+
+                ifmt = CType(Expression, IFormattable)
+
+                Return ifmt.ToString(GetNumberFormatString(NumDigitsAfterDecimal, IncludeLeadingDigit,
+                    UseParensForNegativeNumbers, GroupDigits), Nothing)
+            Catch ex As Exception
+                Throw ex
+            End Try
+        End Function
+
+        Friend Function GetFormatString(ByVal NumDigitsAfterDecimal As Integer,
+            ByVal IncludeLeadingDigit As TriState, ByVal UseParensForNegativeNumbers As TriState,
+            ByVal GroupDigits As TriState, ByVal FormatTypeValue As FormatType) As String
+
+            Dim nfi As NumberFormatInfo
+            Dim sb As StringBuilder
+            Dim sGroup As String
+            Dim sLeadDigit As String
+            Dim sDigitsAfterDecimal As String
+            Dim ci As CultureInfo
+
+            sb = New StringBuilder(30)
+
+            ci = GetCultureInfo()
+            nfi = CType(ci.GetFormat(GetType(System.Globalization.NumberFormatInfo)), NumberFormatInfo)
+
+            If NumDigitsAfterDecimal < -1 Then
+                Throw VbMakeException(vbErrors.IllegalFuncCall)
+            ElseIf NumDigitsAfterDecimal = -1 Then
+                If FormatTypeValue = FormatType.Percent Then
+                    'NOTE: We use NumberDecimalDigits, which is set in the 
+                    ' control panel for VB6 compatibility
+                    ' The urt does not use this setting, but makes a default
+                    ' of their own.
+                    NumDigitsAfterDecimal = nfi.NumberDecimalDigits
+                ElseIf FormatTypeValue = FormatType.Number Then
+                    NumDigitsAfterDecimal = nfi.NumberDecimalDigits
+                ElseIf FormatTypeValue = FormatType.Currency Then
+                    NumDigitsAfterDecimal = nfi.CurrencyDecimalDigits
+                End If
+            End If
+
+            If GroupDigits = TriState.UseDefault Then
+                GroupDigits = TriState.True
+                If FormatTypeValue = FormatType.Percent Then
+                    If IsArrayEmpty(nfi.PercentGroupSizes) Then
+                        GroupDigits = TriState.False
+                    End If
+                ElseIf FormatTypeValue = FormatType.Number Then
+                    If IsArrayEmpty(nfi.NumberGroupSizes) Then
+                        GroupDigits = TriState.False
+                    End If
+                ElseIf FormatTypeValue = FormatType.Currency Then
+                    If IsArrayEmpty(nfi.CurrencyGroupSizes) Then
+                        GroupDigits = TriState.False
+                    End If
+                End If
+            End If
+
+            If UseParensForNegativeNumbers = TriState.UseDefault Then
+                UseParensForNegativeNumbers = TriState.False
+                'If FormatTypeValue = FormatType.Percent Then
+                '    If nfi.PercentNegativePattern = 0 Then
+                '        UseParensForNegativeNumbers = TriState.True
+                '    End If
+                'Else
+
+                If FormatTypeValue = FormatType.Number Then
+                    If nfi.NumberNegativePattern = 0 Then
+                        UseParensForNegativeNumbers = TriState.True
+                    End If
+                ElseIf FormatTypeValue = FormatType.Currency Then
+                    If nfi.CurrencyNegativePattern = 0 Then
+                        UseParensForNegativeNumbers = TriState.True
+                    End If
+                End If
+            End If
+
+            If GroupDigits = TriState.True Then
+                sGroup = "#,##"
+            Else
+                sGroup = ""
+            End If
+
+            If IncludeLeadingDigit <> TriState.False Then
+                sLeadDigit = "0"
+            Else
+                sLeadDigit = "#"
+            End If
+
+            If NumDigitsAfterDecimal > 0 Then
+                sDigitsAfterDecimal = "." & (New System.String("0"c, NumDigitsAfterDecimal))
+            Else
+                sDigitsAfterDecimal = ""
+            End If
+
+            'Now put together the string
+            If FormatTypeValue = FormatType.Currency Then
+                sb.Append(nfi.CurrencySymbol)
+            End If
+
+            sb.Append(sGroup)
+            sb.Append(sLeadDigit)
+            sb.Append(sDigitsAfterDecimal)
+
+            If FormatTypeValue = FormatType.Percent Then
+                sb.Append(nfi.PercentSymbol)
+            End If
+
+            If UseParensForNegativeNumbers = TriState.True Then
+                Dim sTmp As String
+                sTmp = sb.ToString()
+                sb.Append(";(")
+                sb.Append(sTmp)
+                sb.Append(")")
+            End If
+
+            Return sb.ToString()
+        End Function
+
+        Friend Function GetCurrencyFormatString(
+            ByVal IncludeLeadingDigit As TriState,
+            ByVal NumDigitsAfterDecimal As Integer,
+            ByVal UseParensForNegativeNumbers As TriState,
+            ByVal GroupDigits As TriState,
+            ByRef formatProvider As IFormatProvider) As String
+
+            Dim nfi As NumberFormatInfo
+            Dim ci As CultureInfo
+            Dim CurrencyNegativePattern, CurrencyPositivePattern As Integer
+            Dim FormatString, NumberFormat As String
+
+            GetCurrencyFormatString = "C"
+
+            ci = GetCultureInfo()
+            nfi = CType(ci.GetFormat(GetType(System.Globalization.NumberFormatInfo)), NumberFormatInfo)
+            nfi = CType(nfi.Clone(), NumberFormatInfo)
+
+            If GroupDigits = TriState.False Then
+                nfi.CurrencyGroupSizes = New Int32() {0}
+            End If
+
+            CurrencyPositivePattern = nfi.CurrencyPositivePattern
+            CurrencyNegativePattern = nfi.CurrencyNegativePattern
+
+            If UseParensForNegativeNumbers = TriState.UseDefault Then
+
+                Select Case CurrencyNegativePattern
+                    Case 0, 4, 14, 15
+                        UseParensForNegativeNumbers = TriState.True
+                    Case Else
+                        UseParensForNegativeNumbers = TriState.False
+                End Select
+
+            ElseIf UseParensForNegativeNumbers = TriState.False Then
+
+                Select Case CurrencyNegativePattern
+                    Case 0
+                        CurrencyNegativePattern = 1
+                    Case 4
+                        CurrencyNegativePattern = 5
+                    Case 14
+                        CurrencyNegativePattern = 9
+                    Case 15
+                        CurrencyNegativePattern = 10
+                End Select
+
+            Else
+
+                UseParensForNegativeNumbers = TriState.True
+
+                Select Case CurrencyNegativePattern
+                    Case 1, 2, 3    'leading $ w/o space
+                        CurrencyNegativePattern = 0
+                    Case 5, 6, 7    'trailing $ w/o space
+                        CurrencyNegativePattern = 4
+                    Case 8, 10, 13  'Trailing $ / leading with space
+                        CurrencyNegativePattern = 15
+                    Case 9, 11, 12
+                        CurrencyNegativePattern = 14
+                End Select
+
+            End If
+
+            nfi.CurrencyNegativePattern = CurrencyNegativePattern
+
+            If NumDigitsAfterDecimal = -1 Then
+                NumDigitsAfterDecimal = nfi.CurrencyDecimalDigits
+            End If
+            nfi.CurrencyDecimalDigits = NumDigitsAfterDecimal
+
+            formatProvider = New FormatInfoHolder(nfi)
+
+            If IncludeLeadingDigit = TriState.False Then
+                'We need to build our own string in this case, since the NDP does not
+                ' make this accessible
+
+                nfi.NumberGroupSizes = nfi.CurrencyGroupSizes
+
+                FormatString = CurrencyPositiveFormatStrings(CurrencyPositivePattern) & ";" &
+                    CurrencyNegativeFormatStrings(CurrencyNegativePattern)
+
+                If GroupDigits = TriState.False Then
+                    If IncludeLeadingDigit = TriState.False Then
+                        NumberFormat = "#"
+                    Else
+                        NumberFormat = "0"
+                    End If
+                Else
+                    If IncludeLeadingDigit = TriState.False Then
+                        NumberFormat = "#,###"
+                    Else
+                        NumberFormat = "#,##0"
+                    End If
+                End If
+
+                If NumDigitsAfterDecimal > 0 Then
+                    NumberFormat = NumberFormat & "." & New String("0"c, NumDigitsAfterDecimal)
+                End If
+
+                If System.String.CompareOrdinal("$", nfi.CurrencySymbol) <> 0 Then
+                    'Replace the '$' sign with the locale specific symbol
+                    'Note, the currency symbol in the FormatString is surrounded by the literal symbol ', e.g. '$'
+                    'We do this to guard against the case where the currency symbol is the literal symbol \  This was causing problems on Japanese
+                    'systems because that meant our format string ended up as "\#,###.00" when we wanted "'\'#,###.00"  But because the currency symbol
+                    'we are replacing with could concievably be the literal symbol ' as well, we need to make sure we don't end up with an invalid string like "'''#,###.00"
+                    'So if the currency symbol is a ' we replace it with '' so that our format string will be balanced like "''''#,###.00" which will result in the format
+                    'succeeding.  You won't see the ' as the currency symbol in this case but this was never supported anyway.
+                    FormatString = FormatString.Replace("$", nfi.CurrencySymbol.Replace("'", "''"))
+                End If
+
+                Return FormatString.Replace("n", NumberFormat)
+            End If
+
+        End Function
+
+        Friend Function GetNumberFormatString(
+            ByVal NumDigitsAfterDecimal As Integer,
+            ByVal IncludeLeadingDigit As TriState,
+            ByVal UseParensForNegativeNumbers As TriState,
+            ByVal GroupDigits As TriState) As String
+
+            Dim nfi As NumberFormatInfo
+            Dim ci As CultureInfo
+            Dim NumberNegativePattern As Integer
+            Dim FormatString, NumberFormat As String
+
+            ci = GetCultureInfo()
+            nfi = CType(ci.GetFormat(GetType(System.Globalization.NumberFormatInfo)), NumberFormatInfo)
+
+            If NumDigitsAfterDecimal = -1 Then
+                NumDigitsAfterDecimal = nfi.NumberDecimalDigits
+            ElseIf (NumDigitsAfterDecimal > 99) OrElse (NumDigitsAfterDecimal < -1) Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_Range0to99_1, "NumDigitsAfterDecimal"))
+            End If
+
+            If GroupDigits = TriState.UseDefault Then
+                If nfi.NumberGroupSizes Is Nothing OrElse nfi.NumberGroupSizes.Length = 0 Then
+                    GroupDigits = TriState.False
+                Else
+                    GroupDigits = TriState.True
+                End If
+            End If
+
+            NumberNegativePattern = nfi.NumberNegativePattern
+
+            'Value Associated Pattern 
+            '0 (n) 
+            '1 - n  
+            '2 - n  
+            '3 n - 
+            '4 n - 
+            If UseParensForNegativeNumbers = TriState.UseDefault Then
+                Select Case NumberNegativePattern
+                    Case 0
+                        UseParensForNegativeNumbers = TriState.True
+                    Case Else
+                        UseParensForNegativeNumbers = TriState.False
+                End Select
+            ElseIf UseParensForNegativeNumbers = TriState.False Then
+                If NumberNegativePattern = 0 Then
+                    NumberNegativePattern = 1
+                End If
+            Else
+                UseParensForNegativeNumbers = TriState.True
+
+                Select Case NumberNegativePattern
+                    Case 1, 2, 3, 4
+                        NumberNegativePattern = 0
+                End Select
+            End If
+
+            If UseParensForNegativeNumbers = TriState.UseDefault Then
+                UseParensForNegativeNumbers = TriState.True
+            End If
+
+            FormatString = "n;" & NumberNegativeFormatStrings(NumberNegativePattern)
+            If System.String.CompareOrdinal("-", nfi.NegativeSign) <> 0 Then
+                'Replace the "-" sign with the actual locale-specific symbol (escaped with quotes).
+                '  Note: there appears to be no performance benefit in using a StringBuilder over simple concats.
+                FormatString = FormatString.Replace("-", """" & nfi.NegativeSign & """")
+            End If
+
+            If IncludeLeadingDigit <> TriState.False Then
+                NumberFormat = "0"
+            Else
+                NumberFormat = "#"
+            End If
+
+            If GroupDigits = TriState.False OrElse nfi.NumberGroupSizes.Length = 0 Then
+                'Just use setting done above '#' or '0'
+            Else
+                If nfi.NumberGroupSizes.Length = 1 Then
+                    NumberFormat = "#," & New String("#"c, nfi.NumberGroupSizes(0)) & NumberFormat
+                Else
+                    Dim i As Integer
+
+                    NumberFormat = New String("#"c, nfi.NumberGroupSizes(0) - 1) & NumberFormat
+                    For i = 1 To nfi.NumberGroupSizes.GetUpperBound(0)
+                        NumberFormat = "," & New String("#"c, nfi.NumberGroupSizes(i)) & "," & NumberFormat
+                    Next i
+                End If
+            End If
+
+            If NumDigitsAfterDecimal > 0 Then
+                NumberFormat = NumberFormat & "." & New String("0"c, NumDigitsAfterDecimal)
+            End If
+
+            Return Replace(FormatString, "n", NumberFormat)
+        End Function
+
+        Public Function FormatPercent(ByVal Expression As Object,
+            Optional ByVal NumDigitsAfterDecimal As Integer = -1,
+            Optional ByVal IncludeLeadingDigit As TriState = TriState.UseDefault,
+            Optional ByVal UseParensForNegativeNumbers As TriState = TriState.UseDefault,
+            Optional ByVal GroupDigits As TriState = TriState.UseDefault) As String
+
+            Dim ifmt As IFormattable
+            Dim typ As Type
+            Dim sFormat As String
+
+            ValidateTriState(IncludeLeadingDigit)
+            ValidateTriState(UseParensForNegativeNumbers)
+            ValidateTriState(GroupDigits)
+
+            If Expression Is Nothing Then
+                Return ""
+            End If
+
+            typ = Expression.GetType()
+
+            If typ Is GetType(System.String) Then
+                Expression = CDbl(Expression)
+            ElseIf Not Symbols.IsNumericType(typ) Then
+                Throw New InvalidCastException(GetResourceString(SR.InvalidCast_FromTo, VBFriendlyName(typ), "numeric"))
+            End If
+
+            ifmt = CType(Expression, IFormattable)
+            sFormat = GetFormatString(NumDigitsAfterDecimal, IncludeLeadingDigit, UseParensForNegativeNumbers,
+                 GroupDigits, FormatType.Percent)
+            Return ifmt.ToString(sFormat, Nothing)
+        End Function
+
+        '============================================================================
+        ' GetChar function (new for VB7)
+        '============================================================================
+        Public Function GetChar(ByVal [str] As String, ByVal Index As Integer) As Char
+            If [str] Is Nothing Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_LengthGTZero1, "String"))
+            ElseIf (Index < 1) Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_GEOne1, "Index"))
+            ElseIf (Index > [str].Length) Then
+                Throw New ArgumentException(GetResourceString(SR.Argument_IndexLELength2, "Index", "String"))
+            Else
+                Return [str].Chars(Index - 1)
+            End If
+        End Function
+
         '============================================================================
         ' Left/Right/Mid/Trim functions.
         '============================================================================
@@ -711,5 +2040,271 @@ EmptyMatchString:
                 Throw ex
             End Try
         End Function
+
+        Friend Function IsValidCodePage(ByVal codepage As Integer) As Boolean
+            IsValidCodePage = False
+
+            Try
+                If Encoding.GetEncoding(codepage) IsNot Nothing Then
+                    IsValidCodePage = True
+                End If
+            Catch ex As StackOverflowException
+                Throw ex
+            Catch ex As OutOfMemoryException
+                Throw ex
+            Catch ex As System.Threading.ThreadAbortException
+                Throw ex
+            Catch
+            End Try
+        End Function
+
+        Public Function StrConv(ByVal [str] As String, ByVal Conversion As VbStrConv, Optional ByVal LocaleID As Integer = 0) As String
+#If PLATFORM_WINDOWS Then
+            Try
+                Const LANG_CHINESE As Integer = &H4I
+                Const LANG_JAPANESE As Integer = &H11I
+                Const LANG_KOREAN As Integer = &H12I
+                Dim dwMapFlags As Integer
+                Dim loc As CultureInfo
+                Dim langid As Integer
+
+                If (LocaleID = 0 OrElse LocaleID = 1) Then
+                    loc = GetCultureInfo()
+                    LocaleID = loc.LCID()
+                Else
+                    Try
+                        loc = New CultureInfo(LocaleID And &HFFFFI)
+                    Catch ex As StackOverflowException
+                        Throw ex
+                    Catch ex As OutOfMemoryException
+                        Throw ex
+                    Catch ex As System.Threading.ThreadAbortException
+                        Throw ex
+                    Catch
+                        Throw New ArgumentException(GetResourceString(SR.Argument_LCIDNotSupported1, CStr(LocaleID)))
+                    End Try
+                End If
+
+                langid = PRIMARYLANGID(LocaleID)
+
+                'Ensure only valid bits for Conversion are passed in.
+                If (Conversion And Not (VbStrConv.Uppercase Or VbStrConv.Lowercase Or VbStrConv.Wide Or VbStrConv.Narrow _
+                    Or VbStrConv.Katakana Or VbStrConv.Hiragana Or VbStrConv.SimplifiedChinese Or VbStrConv.TraditionalChinese _
+                    Or VbStrConv.LinguisticCasing)) <> 0 Then
+                    Throw New ArgumentException(GetResourceString(SR.Argument_InvalidVbStrConv))
+                End If
+
+                '*** VbStrConv.SimplifiedChinese/VbStrConv.TraditionalChinese handling
+                Select Case (Conversion And (VbStrConv.SimplifiedChinese + VbStrConv.TraditionalChinese))
+
+                    Case 0
+                        'Flags not used
+                    Case (VbStrConv.SimplifiedChinese + VbStrConv.TraditionalChinese)
+                        Throw New ArgumentException(GetResourceString(SR.Argument_StrConvSCandTC))
+                    Case VbStrConv.SimplifiedChinese
+                        If IsValidCodePage(CODEPAGE_SIMPLIFIED_CHINESE) AndAlso IsValidCodePage(CODEPAGE_TRADITIONAL_CHINESE) Then
+                            dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_SIMPLIFIED_CHINESE
+                        Else
+                            Throw New ArgumentException(GetResourceString(SR.Argument_SCNotSupported))
+                        End If
+                    Case VbStrConv.TraditionalChinese
+                        If IsValidCodePage(CODEPAGE_SIMPLIFIED_CHINESE) AndAlso IsValidCodePage(CODEPAGE_TRADITIONAL_CHINESE) Then
+                            dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_TRADITIONAL_CHINESE
+                        Else
+                            Throw New ArgumentException(GetResourceString(SR.Argument_TCNotSupported))
+                        End If
+                End Select
+
+                '*** Upper/Lowercase handling
+                Select Case (Conversion And (VbStrConv.Uppercase Or VbStrConv.Lowercase))
+                    Case VbStrConv.None
+                        'No conversion
+                        If (Conversion And VbStrConv.LinguisticCasing) <> 0 Then
+                            Throw New ArgumentException(GetResourceString(SR.LinguisticRequirements))
+                        End If
+
+                    Case (VbStrConv.Uppercase Or VbStrConv.Lowercase)       '  VbStrConv.ProperCase is special: see below    
+                        'Proper casing gets done below
+                        dwMapFlags = 0
+                    Case VbStrConv.Uppercase
+                        If Conversion = VbStrConv.Uppercase Then
+                            Return loc.TextInfo.ToUpper(str)
+                        Else
+                            dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_UPPERCASE
+                        End If
+                    Case VbStrConv.Lowercase
+                        If Conversion = VbStrConv.Lowercase Then
+                            Return loc.TextInfo.ToLower(str)
+                        Else
+                            dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_LOWERCASE
+                        End If
+                End Select
+
+                If ((Conversion And (VbStrConv.Katakana + VbStrConv.Hiragana)) <> 0) Then
+                    If (langid <> LANG_JAPANESE) OrElse (Not ValidLCID(LocaleID)) Then
+                        Throw New ArgumentException(GetResourceString(SR.Argument_JPNNotSupported))
+                    Else
+                        'Locale is ok
+                    End If
+                End If
+
+                If (Conversion And (VbStrConv.Wide Or VbStrConv.Narrow)) <> 0 Then
+                    If (langid = LANG_JAPANESE) OrElse
+                       (langid = LANG_KOREAN) OrElse
+                       (langid = LANG_CHINESE) Then
+                        If Not ValidLCID(LocaleID) Then
+                            Throw New ArgumentException(GetResourceString(SR.Argument_LocalNotSupported))
+                        End If
+                    Else
+                        Throw New ArgumentException(GetResourceString(SR.Argument_WideNarrowNotApplicable))
+                    End If
+                End If
+
+                '***  Width handling
+                Select Case (Conversion And (VbStrConv.Wide Or VbStrConv.Narrow))
+                    Case VbStrConv.None
+                    Case VbStrConv.Wide Or VbStrConv.Narrow  '  VbStrConv.Wide+VbStrConv.Narrow is reserved
+                        Throw New ArgumentException(GetResourceString(SR.Argument_IllegalWideNarrow))
+                    Case VbStrConv.Wide             '  VbStrConv.Wide
+                        dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_FULLWIDTH
+                    Case VbStrConv.Narrow           '  VbStrConv.Narrow
+                        dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_HALFWIDTH
+                End Select
+
+                '*** Kana handling
+                Select Case (Conversion And (VbStrConv.Katakana Or VbStrConv.Hiragana))
+                    Case VbStrConv.None
+                    Case (VbStrConv.Katakana Or VbStrConv.Hiragana)  '  VbStrConv.Katakana+VbStrConv.Hiragana is reserved
+                        Throw New ArgumentException(GetResourceString(SR.Argument_IllegalKataHira))
+                    Case VbStrConv.Katakana '  VbStrConv.Katakana
+                        dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_KATAKANA
+                    Case VbStrConv.Hiragana '  VbStrConv.Hiragana
+                        dwMapFlags = dwMapFlags Or NativeTypes.LCMAP_HIRAGANA
+                End Select
+
+                ' accents field (Conversion And 192) in Conversion is reserved
+                If ((Conversion And VbStrConv.ProperCase) = VbStrConv.ProperCase) Then
+                    Return ProperCaseString(loc, dwMapFlags, [str])
+                ElseIf dwMapFlags <> 0 Then
+                    Return vbLCMapString(loc, dwMapFlags, [str])
+                Else
+                    Return [str]
+                End If
+            Catch ex As Exception
+                Throw ex
+            End Try
+#Else
+            Throw New PlatformNotSupportedException()
+#End If
+        End Function
+
+#If PLATFORM_WINDOWS Then
+        Friend Function ValidLCID(ByVal LocaleID As Integer) As Boolean
+            Try
+                Dim loc As CultureInfo = New CultureInfo(LocaleID)
+                ValidLCID = True
+            Catch ex As StackOverflowException
+                Throw ex
+            Catch ex As OutOfMemoryException
+                Throw ex
+            Catch ex As System.Threading.ThreadAbortException
+                Throw ex
+            Catch
+                ValidLCID = False
+            End Try
+        End Function
+
+        Private Function ProperCaseString(ByVal loc As CultureInfo, ByVal dwMapFlags As Integer, ByVal sSrc As String) As String
+            Dim iSrcLen As Integer
+            Dim sb As StringBuilder
+
+            If sSrc Is Nothing Then
+                iSrcLen = 0
+            Else
+                iSrcLen = sSrc.Length
+            End If
+
+            If iSrcLen = 0 Then
+                Return ""
+            End If
+
+            '   do the mapping specified by dwMapFlags, and at the same time, lowercase
+            '   the whole string
+            sb = New StringBuilder(vbLCMapString(loc, dwMapFlags Or NativeTypes.LCMAP_LOWERCASE, sSrc))
+
+            'ToTitleCase is a more linguistically correct casing for the current locale
+            Return loc.TextInfo.ToTitleCase(sb.ToString())
+
+        End Function
+
+        <ResourceExposure(ResourceScope.None)>
+        <ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)>
+        Friend Function vbLCMapString(ByVal loc As CultureInfo, ByVal dwMapFlags As Integer, ByVal sSrc As String) As String
+            Dim length As Integer
+
+            If sSrc Is Nothing Then
+                length = 0
+            Else
+                length = sSrc.Length
+            End If
+
+            If length = 0 Then
+                Return ""
+            End If
+
+            Dim sDest As String
+            Dim lenDest As Integer
+            Dim lcid As Integer = loc.LCID
+            Dim enc As Text.Encoding = Text.Encoding.GetEncoding(loc.TextInfo.ANSICodePage)
+
+            If Not enc.IsSingleByte Then
+
+                'VB6 syntax note: ByVal String in Declare statements is really a ByRef String syntax
+                'So sTemp will always be updated here on copyback 
+                Dim sTemp As String = sSrc
+                Dim bytesSrc, bytesDest As Byte()
+
+                'Forced to use ANSI here
+                'Char count can actual increase or decrease
+
+                'Get byte array
+                bytesSrc = enc.GetBytes(sTemp)
+
+                'Get required byte length for new destination
+                lenDest = UnsafeNativeMethods.LCMapStringA(lcid, dwMapFlags, bytesSrc, bytesSrc.Length, Nothing, 0)
+
+                'Create destination byte array of required length
+                bytesDest = New Byte(lenDest - 1) {}
+
+                'Call again to do the actual translation
+                lenDest = UnsafeNativeMethods.LCMapStringA(lcid, dwMapFlags, bytesSrc, bytesSrc.Length, bytesDest, lenDest)
+
+                'Now convert back to a string
+                sDest = enc.GetString(bytesDest)
+
+                Return sDest
+
+            Else
+                'We do not use StringBuilder here because embedded NULLs cause an early termination of the string
+                sDest = New String(" "c, length)
+                lenDest = UnsafeNativeMethods.LCMapString(lcid, dwMapFlags, sSrc, length, sDest, length)
+                Return sDest
+            End If
+
+        End Function
+#End If
+
+        Private Sub ValidateTriState(ByVal Param As TriState)
+            If (Param <> vbTrue) AndAlso (Param <> vbFalse) AndAlso (Param <> vbUseDefault) Then
+                Throw VbMakeException(vbErrors.IllegalFuncCall)
+            End If
+        End Sub
+
+        Private Function IsArrayEmpty(ByVal array As System.Array) As Boolean
+            If array Is Nothing Then
+                Return True
+            End If
+            Return (array.Length = 0)
+        End Function
     End Module
 End Namespace
index e099fe79a75528e007cf3c926c2b4ffb8b0bde6a..bf80010ec1ac2254699d06930fa24703be309b62 100644 (file)
@@ -3,8 +3,9 @@
 // See the LICENSE file in the project root for more information.
 
 using System;
-using System.Diagnostics;
+using System.Collections.Generic;
 using System.Globalization;
+using System.Runtime.InteropServices;
 using System.Text;
 using Microsoft.VisualBasic.CompilerServices.Tests;
 using Microsoft.DotNet.RemoteExecutor;
@@ -189,6 +190,187 @@ namespace Microsoft.VisualBasic.Tests
             Assert.Equal(excludeExpected, Strings.Filter(source, match, Include: false));
         }
 
+        [Theory]
+        [MemberData(nameof(Format_TestData))]
+        public void Format(object expression, string style, string expected)
+        {
+            Assert.Equal(expected, Strings.Format(expression, style));
+        }
+
+        [Theory]
+        [MemberData(nameof(Format_InvalidCastException_TestData))]
+        public void Format_InvalidCastException(object expression, string style)
+        {
+            Assert.Throws<InvalidCastException>(() => Strings.Format(expression, style));
+        }
+
+        private static IEnumerable<object[]> Format_TestData()
+        {
+            yield return new object[] { null, null, "" };
+            yield return new object[] { null, "", "" };
+            yield return new object[] { "", null, "" };
+            yield return new object[] { "", "", "" };
+            yield return new object[] { sbyte.MinValue, "0", "-128" };
+            yield return new object[] { sbyte.MaxValue, "0", "127" };
+            yield return new object[] { ushort.MinValue, "0", "0" };
+            yield return new object[] { ushort.MaxValue, "0", "65535" };
+            yield return new object[] { false, "", "False" };
+            yield return new object[] { false, "0", "0" };
+            if (IsEnUS())
+            {
+                yield return new object[] { 1.234, "", "1.234" };
+                yield return new object[] { 1.234, "0", "1" };
+                yield return new object[] { 1.234, "0.0", "1.2" };
+                yield return new object[] { 1.234, "fixed", "1.23" };
+                yield return new object[] { 1.234, "percent", "123.40%" };
+                yield return new object[] { 1.234, "standard", "1.23" };
+                yield return new object[] { 1.234, "currency", "$1.23" };
+                yield return new object[] { false, "yes/no", "No" };
+                yield return new object[] { true, "yes/no", "Yes" };
+                yield return new object[] { false, "on/off", "Off" };
+                yield return new object[] { true, "on/off", "On" };
+                yield return new object[] { false, "true/false", "False" };
+                yield return new object[] { true, "true/false",  "True" };
+                yield return new object[] { 0, "yes/no", "No" };
+                yield return new object[] { "ABC", "yes/no", "ABC" };
+                yield return new object[] { 123.4, "scientific", "1.23E+02" };
+            }
+            DateTime d = DateTime.Now;
+            yield return new object[] { d, "long time", d.ToString("T") };
+            yield return new object[] { d, "medium time", d.ToString("T") };
+            yield return new object[] { d, "short time", d.ToString("t") };
+            yield return new object[] { d, "long date", d.ToString("D") };
+            yield return new object[] { d, "medium date", d.ToString("D") };
+            yield return new object[] { d, "short date", d.ToString("d") };
+            yield return new object[] { d, "general date", d.ToString("G") };
+            yield return new object[] { 123.4, "general number", 123.4.ToString("G", null) };
+        }
+
+        private static IEnumerable<object[]> Format_InvalidCastException_TestData()
+        {
+            yield return new object[] { new object(), null };
+            yield return new object[] { new object(), "0" };
+        }
+
+        [Theory]
+        [MemberData(nameof(FormatCurrency_TestData))]
+        public void FormatCurrency(object expression, int numDigitsAfterDecimal, TriState includeLeadingDigit, TriState useParensForNegativeNumbers, TriState groupDigits, string expected)
+        {
+            Assert.Equal(expected, Strings.FormatCurrency(expression, numDigitsAfterDecimal, includeLeadingDigit, useParensForNegativeNumbers, groupDigits));
+        }
+
+        private static IEnumerable<object[]> FormatCurrency_TestData()
+        {
+            yield return new object[] { null, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "" };
+            if (IsEnUS())
+            {
+                yield return new object[] { 0.123, 0, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "$0" };
+                yield return new object[] { 0.123, 1, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "$0.1" };
+                yield return new object[] { 0.123, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "$0.12" };
+                yield return new object[] { 0.123, 4, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "$0.1230" };
+                yield return new object[] { 0.123, 2, TriState.False, TriState.UseDefault, TriState.UseDefault, "$.12" };
+                yield return new object[] { 0.123, 2, TriState.True, TriState.UseDefault, TriState.UseDefault, "$0.12" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.False, TriState.UseDefault, "-$0.12" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.True, TriState.UseDefault, "($0.12)" };
+                yield return new object[] { 1234.5, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "$1,234.50" };
+                yield return new object[] { 1234.5, 2, TriState.UseDefault, TriState.UseDefault, TriState.False, "$1234.50" };
+                yield return new object[] { 1234.5, 2, TriState.UseDefault, TriState.UseDefault, TriState.True, "$1,234.50" };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(FormatDateTime_TestData))]
+        public void FormatDateTime(DateTime expression, DateFormat format, string expected)
+        {
+            Assert.Equal(expected, Strings.FormatDateTime(expression, format));
+        }
+
+        private static IEnumerable<object[]> FormatDateTime_TestData()
+        {
+            DateTime d = DateTime.Now;
+            yield return new object[] { d, DateFormat.LongTime, d.ToString("T") };
+            yield return new object[] { d, DateFormat.ShortTime, d.ToString("HH:mm") };
+            yield return new object[] { d, DateFormat.LongDate, d.ToString("D") };
+            yield return new object[] { d, DateFormat.ShortDate, d.ToString("d") };
+            yield return new object[] { d, DateFormat.GeneralDate, d.ToString("G") };
+        }
+
+        [Theory]
+        [MemberData(nameof(FormatNumber_TestData))]
+        public void FormatNumber(object expression, int numDigitsAfterDecimal, TriState includeLeadingDigit, TriState useParensForNegativeNumbers, TriState groupDigits, string expected)
+        {
+            Assert.Equal(expected, Strings.FormatNumber(expression, numDigitsAfterDecimal, includeLeadingDigit, useParensForNegativeNumbers, groupDigits));
+        }
+
+        private static IEnumerable<object[]> FormatNumber_TestData()
+        {
+            yield return new object[] { null, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "" };
+            if (IsEnUS())
+            {
+                yield return new object[] { 0.123, 0, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "0" };
+                yield return new object[] { 0.123, 1, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "0.1" };
+                yield return new object[] { 0.123, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "0.12" };
+                yield return new object[] { 0.123, 4, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "0.1230" };
+                yield return new object[] { 0.123, 2, TriState.False, TriState.UseDefault, TriState.UseDefault, ".12" };
+                yield return new object[] { 0.123, 2, TriState.True, TriState.UseDefault, TriState.UseDefault, "0.12" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "-0.12" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.False, TriState.UseDefault, "-0.12" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.True, TriState.UseDefault, "(0.12)" };
+                yield return new object[] { 1234.5, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "1,234.50" };
+                yield return new object[] { 1234.5, 2, TriState.UseDefault, TriState.UseDefault, TriState.False, "1234.50" };
+                yield return new object[] { 1234.5, 2, TriState.UseDefault, TriState.UseDefault, TriState.True, "1,234.50" };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(FormatPercent_TestData))]
+        public void FormatPercent(object expression, int numDigitsAfterDecimal, TriState includeLeadingDigit, TriState useParensForNegativeNumbers, TriState groupDigits, string expected)
+        {
+            Assert.Equal(expected, Strings.FormatPercent(expression, numDigitsAfterDecimal, includeLeadingDigit, useParensForNegativeNumbers, groupDigits));
+        }
+
+        private static IEnumerable<object[]> FormatPercent_TestData()
+        {
+            yield return new object[] { null, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "" };
+            if (IsEnUS())
+            {
+                yield return new object[] { 0.123, 0, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "12%" };
+                yield return new object[] { 0.123, 1, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "12.3%" };
+                yield return new object[] { 0.123, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "12.30%" };
+                yield return new object[] { 0.123, 4, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "12.3000%" };
+                yield return new object[] { 0.00123, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "0.12%" };
+                yield return new object[] { 0.00123, 2, TriState.False, TriState.UseDefault, TriState.UseDefault, ".12%" };
+                yield return new object[] { 0.00123, 2, TriState.True, TriState.UseDefault, TriState.UseDefault, "0.12%" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "-12.30%" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.False, TriState.UseDefault, "-12.30%" };
+                yield return new object[] { -0.123, 2, TriState.UseDefault, TriState.True, TriState.UseDefault, "(12.30%)" };
+                yield return new object[] { 12.345, 2, TriState.UseDefault, TriState.UseDefault, TriState.UseDefault, "1,234.50%" };
+                yield return new object[] { 12.345, 2, TriState.UseDefault, TriState.UseDefault, TriState.False, "1234.50%" };
+                yield return new object[] { 12.345, 2, TriState.UseDefault, TriState.UseDefault, TriState.True, "1,234.50%" };
+            }
+        }
+
+        [Theory]
+        [InlineData("ABC", 1, 'A')]
+        [InlineData("ABC", 2, 'B')]
+        [InlineData("ABC", 3, 'C')]
+        public void GetChar(string str, int index, char expected)
+        {
+            Assert.Equal(expected, Strings.GetChar(str, index));
+        }
+
+        [Theory]
+        [InlineData(null, 0)]
+        [InlineData(null, 1)]
+        [InlineData("", 0)]
+        [InlineData("", 1)]
+        [InlineData("ABC", 0)]
+        [InlineData("ABC", 4)]
+        public void GetChar_ArgumentException(string str, int index)
+        {
+            Assert.Throws< ArgumentException>(() => Strings.GetChar(str, index));
+        }
+
         [Theory]
         [MemberData(nameof(InStr_TestData_NullsAndEmpties))]
         [MemberData(nameof(InStr_FromBegin_TestData))]
@@ -294,6 +476,89 @@ namespace Microsoft.VisualBasic.Tests
             AssertExtensions.Throws<ArgumentException>("Start", null, () => Strings.InStrRev("a", "a", start));
         }
 
+        [Theory]
+        [MemberData(nameof(Join_Object_TestData))]
+        [MemberData(nameof(Join_String_TestData))]
+        public void Join(object[] source, string delimiter, string expected)
+        {
+            Assert.Equal(expected, Strings.Join(source, delimiter));
+        }
+
+        private static IEnumerable<object[]> Join_Object_TestData()
+        {
+            yield return new object[] { new object[0], null, null };
+            yield return new object[] { new object[0], ",", null };
+            yield return new object[] { new object[] { 1 }, ",", "1" };
+            yield return new object[] { new object[] { 1, null, 3 }, null, "13" };
+            yield return new object[] { new object[] { true, false }, "", "TrueFalse" };
+            yield return new object[] { new object[] { 1, 2, 3 }, ", ", "1, 2, 3" };
+        }
+
+        [Theory]
+        [MemberData(nameof(Join_String_TestData))]
+        public void Join(string[] source, string delimiter, string expected)
+        {
+            Assert.Equal(expected, Strings.Join(source, delimiter));
+        }
+
+        private static IEnumerable<object[]> Join_String_TestData()
+        {
+            yield return new object[] { new string[0], null, null };
+            yield return new object[] { new string[0], ",", null };
+            yield return new object[] { new string[] { "A" }, ",", "A" };
+            yield return new object[] { new string[] { "", null, "" }, null, "" };
+            yield return new object[] { new string[] { "", "AB", "C" }, "", "ABC" };
+            yield return new object[] { new string[] { "A", "B", "C" }, ", ", "A, B, C" };
+        }
+
+        [Theory]
+        [InlineData('\0', "\0")]
+        [InlineData('\uffff', "\uffff")]
+        [InlineData('a', "a")]
+        [InlineData('A', "a")]
+        [InlineData('1', "1")]
+        public void LCase(char value, char expected)
+        {
+            Assert.Equal(expected, Strings.LCase(value));
+        }
+
+        [Theory]
+        [InlineData(null, null)]
+        [InlineData("", "")]
+        [InlineData("\0", "\0")]
+        [InlineData("\uffff", "\uffff")]
+        [InlineData("abc", "abc")]
+        [InlineData("ABC", "abc")]
+        [InlineData("123", "123")]
+        public void LCase(string value, string expected)
+        {
+            Assert.Equal(expected, Strings.LCase(value));
+        }
+
+        [Theory]
+        [InlineData('\0', "\0")]
+        [InlineData('\uffff', "\uffff")]
+        [InlineData('a', "A")]
+        [InlineData('A', "A")]
+        [InlineData('1', "1")]
+        public void UCase(char value, char expected)
+        {
+            Assert.Equal(expected, Strings.UCase(value));
+        }
+
+        [Theory]
+        [InlineData(null, "")]
+        [InlineData("", "")]
+        [InlineData("\0", "\0")]
+        [InlineData("\uffff", "\uffff")]
+        [InlineData("abc", "ABC")]
+        [InlineData("ABC", "ABC")]
+        [InlineData("123", "123")]
+        public void UCase(string value, string expected)
+        {
+            Assert.Equal(expected, Strings.UCase(value));
+        }
+
         [Theory]
         [InlineData("a", -1)]
         public void Left_Invalid(string str, int length)
@@ -401,6 +666,40 @@ namespace Microsoft.VisualBasic.Tests
             Assert.Equal(expected, Strings.Mid(str, start, length));
         }
 
+        [Theory]
+        [InlineData(null, 0, "")]
+        [InlineData(null, 1, " ")]
+        [InlineData("", 0, "")]
+        [InlineData("", 1, " ")]
+        [InlineData("A", 0, "")]
+        [InlineData("A", 1, "A")]
+        [InlineData("A", 2, "A ")]
+        [InlineData("AB", 0, "")]
+        [InlineData("AB", 1, "A")]
+        [InlineData("AB", 2, "AB")]
+        [InlineData("AB", 4, "AB  ")]
+        public void LSet(string source, int length, string expected)
+        {
+            Assert.Equal(expected, Strings.LSet(source, length));
+        }
+
+        [Theory]
+        [InlineData(null, 0, "")]
+        [InlineData(null, 1, " ")]
+        [InlineData("", 0, "")]
+        [InlineData("", 1, " ")]
+        [InlineData("A", 0, "")]
+        [InlineData("A", 1, "A")]
+        [InlineData("A", 2, " A")]
+        [InlineData("AB", 0, "")]
+        [InlineData("AB", 1, "A")]
+        [InlineData("AB", 2, "AB")]
+        [InlineData("AB", 4, "  AB")]
+        public void RSet(string source, int length, string expected)
+        {
+            Assert.Equal(expected, Strings.RSet(source, length));
+        }
+
         [Theory]
         [InlineData(null, "")]
         [InlineData("", "")]
@@ -446,6 +745,68 @@ namespace Microsoft.VisualBasic.Tests
             Assert.Equal(expected, Strings.Trim(str));
         }
 
+        [Theory]
+        [InlineData("", "", null, 1, -1, CompareMethod.Text, null)]
+        [InlineData("", null, "", 1, -1, CompareMethod.Text, null)]
+        [InlineData("", "", "", 1, -1, CompareMethod.Text, null)]
+        [InlineData("ABC", "", "", 1, -1, CompareMethod.Text, "ABC")]
+        [InlineData("ABC", "bc", "23", 1, -1, CompareMethod.Binary, "ABC")]
+        [InlineData("ABC", "BC", "23", 1, -1, CompareMethod.Binary, "A23")]
+        [InlineData("ABC", "bc", "23", 1, -1, CompareMethod.Text, "A23")]
+        [InlineData("abcbc", "bc", "23", 1, -1, CompareMethod.Text, "a2323")]
+        [InlineData("abcbc", "bc", "23", 1, 0, CompareMethod.Text, "abcbc")]
+        [InlineData("abcbc", "bc", "23", 1, 1, CompareMethod.Text, "a23bc")]
+        [InlineData("abc", "bc", "23", 2, -1, CompareMethod.Text, "23")]
+        [InlineData("abc", "bc", "23", 3, -1, CompareMethod.Text, "c")]
+        [InlineData("abc", "bc", "23", 4, -1, CompareMethod.Text, null)]
+        public void Replace(string expression, string find, string replacement, int start, int n, CompareMethod compare, string expected)
+        {
+            Assert.Equal(expected, Strings.Replace(expression, find, replacement, start, n, compare));
+        }
+
+        [Theory]
+        [InlineData(null, null, null, 0, 0, CompareMethod.Text)]
+        [InlineData(null, "", "", 0, 0, CompareMethod.Text)]
+        public void Replace_ArgumentException(string expression, string find, string replacement, int start, int length, CompareMethod compare)
+        {
+            Assert.Throws< ArgumentException>(() => Strings.Replace(expression, find, replacement, start, length, compare));
+        }
+
+        [Theory]
+        [InlineData(0, "")]
+        [InlineData(1, " ")]
+        [InlineData(3, "   ")]
+        public void Space(int number, string expected)
+        {
+            Assert.Equal(expected, Strings.Space(number));
+        }
+
+        [Theory]
+        [InlineData(null, null, -1, CompareMethod.Text, new string[] { "" })]
+        [InlineData(null, "", -1, CompareMethod.Text, new string[] { "" })]
+        [InlineData("", null, -1, CompareMethod.Text, new string[] { "" })]
+        [InlineData("", "", -1, CompareMethod.Text, new string[] { "" })]
+        [InlineData("ABC", ",", -1, CompareMethod.Text, new string[] { "ABC" })]
+        [InlineData("A,,BC", ",", -1, CompareMethod.Text, new string[] { "A", "", "BC" })]
+        [InlineData("A,,BC", ",", -1, CompareMethod.Text, new string[] { "A", "", "BC" })]
+        [InlineData("ABC", "b", -1, CompareMethod.Text, new string[] { "A", "C" })]
+        [InlineData("ABC", "b", -1, CompareMethod.Binary, new string[] { "ABC" })]
+        [InlineData("A, B, C", ", ", -1, CompareMethod.Text, new string[] { "A", "B", "C" })]
+        [InlineData("A, B, C", ", ", 1, CompareMethod.Text, new string[] { "A, B, C" })]
+        [InlineData("A, B, C", ", ", 2, CompareMethod.Text, new string[] { "A", "B, C" })]
+        [InlineData("A, B, C", ", ", int.MaxValue, CompareMethod.Text, new string[] { "A", "B", "C" })]
+        public void Split(string expression, string delimiter, int limit, CompareMethod compare, string[] expected)
+        {
+            Assert.Equal(expected, Strings.Split(expression, delimiter, limit, compare));
+        }
+
+        [Theory]
+        [InlineData("A, B, C", ", ", 0, CompareMethod.Text)]
+        public void Split_IndexOutOfRangeException(string expression, string delimiter, int limit, CompareMethod compare)
+        {
+            Assert.Throws< IndexOutOfRangeException>(() => Strings.Split(expression, delimiter, limit, compare));
+        }
+
         [Theory]
         [InlineData("a", "a", 0, 0)]
         [InlineData("a", "b", -1, -1)]
@@ -459,6 +820,114 @@ namespace Microsoft.VisualBasic.Tests
             Assert.Equal(expectedTextCompare, Strings.StrComp(left, right, CompareMethod.Text));
         }
 
+        [Theory]
+        [InlineData(null, VbStrConv.None, 0, null)]
+        [InlineData("", VbStrConv.None, 0, "")]
+        [InlineData("ABC123", VbStrConv.None, 0, "ABC123")]
+        [InlineData("", VbStrConv.Lowercase, 0, "")]
+        [InlineData("Abc123", VbStrConv.Lowercase, 0, "abc123")]
+        [InlineData("Abc123", VbStrConv.Uppercase, 0, "ABC123")]
+        public void StrConv(string str, Microsoft.VisualBasic.VbStrConv conversion, int localeID, string expected)
+        {
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                Assert.Equal(expected, Strings.StrConv(str, conversion, localeID));
+            }
+            else
+            {
+                Assert.Throws<PlatformNotSupportedException>(() => Strings.StrConv(str, conversion, localeID));
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(StrDup_Object_TestData))]
+        [MemberData(nameof(StrDup_Char_TestData))]
+        [MemberData(nameof(StrDup_String_TestData))]
+        public void StrDup(int number, object character, object expected)
+        {
+            Assert.Equal(expected, Strings.StrDup(number, character));
+        }
+
+        [Theory]
+        [MemberData(nameof(StrDup_Object_ArgumentException_TestData))]
+        public void StrDup_ArgumentException(int number, object character)
+        {
+            Assert.Throws< ArgumentException>(() => Strings.StrDup(number, character));
+        }
+
+        [Theory]
+        [MemberData(nameof(StrDup_Char_TestData))]
+        public void StrDup(int number, char character, string expected)
+        {
+            Assert.Equal(expected, Strings.StrDup(number, character));
+        }
+
+        [Theory]
+        [MemberData(nameof(StrDup_Char_ArgumentException_TestData))]
+        public void StrDup_ArgumentException(int number, char character)
+        {
+            Assert.Throws<ArgumentException>(() => Strings.StrDup(number, character));
+        }
+
+        [Theory]
+        [MemberData(nameof(StrDup_String_TestData))]
+        public void StrDup(int number, string character, string expected)
+        {
+            Assert.Equal(expected, Strings.StrDup(number, character));
+        }
+
+        [Theory]
+        [MemberData(nameof(StrDup_String_ArgumentException_TestData))]
+        public void StrDup_ArgumentException(int number, string character)
+        {
+            Assert.Throws<ArgumentException>(() => Strings.StrDup(number, character));
+        }
+
+        private static IEnumerable<object[]> StrDup_Object_TestData()
+        {
+            yield break;
+        }
+
+        private static IEnumerable<object[]> StrDup_Char_TestData()
+        {
+            yield return new object[] { 3, '\0', "\0\0\0" };
+            yield return new object[] { 0, 'A', "" };
+            yield return new object[] { 1, 'A', "A" };
+            yield return new object[] { 3, 'A', "AAA" };
+        }
+
+        private static IEnumerable<object[]> StrDup_String_TestData()
+        {
+            yield return new object[] { 0, "A", "" };
+            yield return new object[] { 1, "A", "A" };
+            yield return new object[] { 3, "A", "AAA" };
+            yield return new object[] { 0, "ABC", "" };
+            yield return new object[] { 1, "ABC", "A" };
+            yield return new object[] { 3, "ABC", "AAA" };
+        }
+
+        private static IEnumerable<object[]> StrDup_Object_ArgumentException_TestData()
+        {
+            yield return new object[] { -1, new object() };
+            yield return new object[] { 1, 0 };
+            yield return new object[] { 1, (int)'A' };
+            yield return new object[] { -1, 'A' };
+            yield return new object[] { -1, "A" };
+            yield return new object[] { 1, "" };
+        }
+
+        private static IEnumerable<object[]> StrDup_Char_ArgumentException_TestData()
+        {
+            yield return new object[] { -1, 'A' };
+        }
+
+        private static IEnumerable<object[]> StrDup_String_ArgumentException_TestData()
+        {
+            yield return new object[] { -1, "A" };
+            yield return new object[] { 1, null };
+            yield return new object[] { 1, "" };
+        }
+
         [Theory]
         [InlineData(null, "")]
         [InlineData("", "")]
@@ -532,5 +1001,7 @@ namespace Microsoft.VisualBasic.Tests
             { "aa", "ab", 1, 0 },
             { "abab", "ab", 3, 1 },
         };
+
+        private static bool IsEnUS() => System.Threading.Thread.CurrentThread.CurrentUICulture.Name == "en-US";
     }
 }