' Tween - Client of Twitter
' Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
'           (c) 2008-2011 Moz (@syo68k)
'           (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
'           (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
'           (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
' All rights reserved.
' 
' This file is part of Tween.
' 
' This program is free software; 
'
' Copyright © Microsoft Corporation.  All Rights Reserved.
' This code released under the terms of the 
' Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.)
'

Option Strict On
Option Explicit On

Imports System.Linq
Imports System.Linq.Expressions
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Runtime.CompilerServices
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Threading


Public Module DynamicQueryable

    Const Id As String = "DynamicQueryModule Copyright © Microsoft Corporation.  All Rights Reserved." + _
                         " This code released under the terms of the Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) "

    <Extension()> _
    Public Function Where(Of T)(ByVal source As IQueryable(Of T), ByVal predicate As String, ByVal ParamArray values() As Object) As IQueryable(Of T)
        Return DirectCast(Where(DirectCast(source, IQueryable), predicate, values), IQueryable(Of T))
    End Function

    <Extension()> _
    Public Function Where(ByVal source As IQueryable, ByVal predicate As String, ByVal ParamArray values() As Object) As IQueryable
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If predicate Is Nothing Then Throw New ArgumentNullException("predicate")
        Dim lambda As LambdaExpression = DynamicExpression.ParseLambda(source.ElementType, GetType(Boolean), predicate, values)
        Return source.Provider.CreateQuery( _
                                           Expression.Call( _
                                                           GetType(Queryable), "Where", _
                                                           New Type() {source.ElementType}, _
                                                           source.Expression, Expression.Quote(lambda)))
    End Function

    <Extension()> _
    Public Function [Select](ByVal source As IQueryable, ByVal selector As String, ByVal ParamArray values() As Object) As IQueryable
        If source Is Nothing Then Throw New ArgumentNullException("source")
        If selector Is Nothing Then Throw New ArgumentNullException("selector")
        Dim lambda As LambdaExpression = DynamicExpression.ParseLambda(source.ElementType, Nothing, selector, values)
        Return source.Provider.CreateQuery( _
                                           Expression.Call( _
                                                           GetType(Queryable), "Select", _
                                                           New Type() {source.ElementType, lambda.Body.Type}, _
                                                           source.Expression, Expression.Quote(lambda)))
    End Function

    <Extension()> _
    Public Function OrderBy(Of T)(ByVal source As IQueryable(Of T), ByVal ordering As String, ByVal ParamArray values() As Object) As IQueryable(Of T)
        Return DirectCast(OrderBy(DirectCast(source, IQueryable), ordering, values), IQueryable(Of T))
    End Function

    <Extension()> _
    Public Function OrderBy(ByVal source As IQueryable, ByVal ordering As String, ByVal ParamArray values() As Object) As IQueryable
        If (source Is Nothing) Then Throw New ArgumentNullException("source")
        If (ordering Is Nothing) Then Throw New ArgumentNullException("ordering")
        Dim parameters = New ParameterExpression() { _
                                                       Expression.Parameter(source.ElementType, "")}
        Dim parser As New ExpressionParser(parameters, ordering, values)
        Dim orderings As IEnumerable(Of DynamicOrdering) = parser.ParseOrdering()
        Dim queryExpr As Expression = source.Expression
        Dim methodAsc = "OrderBy"
        Dim methodDesc = "OrderByDescending"
        For Each o As DynamicOrdering In orderings
            queryExpr = Expression.Call( _
                                        GetType(Queryable), If(o.Ascending, methodAsc, methodDesc), _
                                        New Type() {source.ElementType, o.Selector.Type}, _
                                        queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)))
            methodAsc = "ThenBy"
            methodDesc = "ThenByDescending"
        Next o
        Return source.Provider.CreateQuery(queryExpr)
    End Function

    <Extension()> _
    Public Function Take(ByVal source As IQueryable, ByVal count As Integer) As IQueryable
        If (source Is Nothing) Then Throw New ArgumentNullException("source")
        Return source.Provider.CreateQuery( _
                                           Expression.Call( _
                                                           GetType(Queryable), "Take", _
                                                           New Type() {source.ElementType}, _
                                                           source.Expression, Expression.Constant(count)))
    End Function

    <Extension()> _
    Public Function Skip(ByVal source As IQueryable, ByVal count As Integer) As IQueryable
        If (source Is Nothing) Then Throw New ArgumentNullException("source")
        Return source.Provider.CreateQuery( _
                                           Expression.Call( _
                                                           GetType(Queryable), "Skip", _
                                                           New Type() {source.ElementType}, _
                                                           source.Expression, Expression.Constant(count)))
    End Function

    <Extension()> _
    Public Function GroupBy(ByVal source As IQueryable, ByVal keySelector As String, ByVal elementSelector As String, ByVal ParamArray values() As Object) As IQueryable
        If (source Is Nothing) Then Throw New ArgumentNullException("source")
        If (keySelector Is Nothing) Then Throw New ArgumentNullException("keySelector")
        If (elementSelector Is Nothing) Then Throw New ArgumentNullException("elementSelector")
        Dim keyLambda As LambdaExpression = DynamicExpression.ParseLambda(source.ElementType, Nothing, keySelector, values)
        Dim elementLambda As LambdaExpression = DynamicExpression.ParseLambda(source.ElementType, Nothing, elementSelector, values)
        Return source.Provider.CreateQuery( _
                                           Expression.Call( _
                                                           GetType(Queryable), "GroupBy", _
                                                           New Type() {source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type}, _
                                                           source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)))
    End Function

    <Extension()> _
    Public Function Any(ByVal source As IQueryable) As Boolean
        If (source Is Nothing) Then Throw New ArgumentNullException("source")
        Return CBool(source.Provider.Execute( _
                                             Expression.Call( _
                                                             GetType(Queryable), "Any", _
                                                             New Type() {source.ElementType}, source.Expression)))
    End Function

    <Extension()> _
    Public Function Count(ByVal source As IQueryable) As Integer
        If (source Is Nothing) Then Throw New ArgumentNullException("source")
        Return CInt(source.Provider.Execute( _
                                            Expression.Call( _
                                                            GetType(Queryable), "Count", _
                                                            New Type() {source.ElementType}, source.Expression)))
    End Function
End Module

Public MustInherit Class DynamicClass
    Public Overrides Function ToString() As String
        Dim props = Me.GetType().GetProperties(BindingFlags.Instance Or BindingFlags.Public)
        Dim sb As New StringBuilder()
        sb.Append("{")
        For i As Integer = 0 To props.Length - 1
            If (i > 0) Then sb.Append(", ")
            sb.Append(props(i).Name)
            sb.Append("=")
            sb.Append(props(i).GetValue(Me, Nothing))
        Next i

        sb.Append("}")

        Return sb.ToString()
    End Function
End Class

Public Class DynamicProperty
    Private _name As String
    Private _type As Type

    Public Sub New(ByVal name As String, ByVal type As Type)
        If (name Is Nothing) Then Throw New ArgumentNullException("name")
        If (type Is Nothing) Then Throw New ArgumentNullException("type")
        Me._name = name
        Me._type = type
    End Sub

    Public ReadOnly Property Name() As String
        Get
            Return _name
        End Get
    End Property

    Public ReadOnly Property Type() As Type
        Get
            Return _type
        End Get
    End Property
End Class

Public Module DynamicExpression
    Public Function Parse(ByVal resultType As Type, ByVal expression As String, ByVal ParamArray values() As Object) As Expression
        Dim parser As New ExpressionParser(Nothing, expression, values)
        Return parser.Parse(resultType)
    End Function

    Public Function ParseLambda(ByVal itType As Type, ByVal resultType As Type, ByVal expressionStr As String, ByVal ParamArray values() As Object) As LambdaExpression
        Return ParseLambda(New ParameterExpression() {Expression.Parameter(itType, "")}, resultType, expressionStr, values)
    End Function

    Public Function ParseLambda(ByVal parameters() As ParameterExpression, ByVal resultType As Type, ByVal expressionStr As String, ByVal ParamArray values() As Object) As LambdaExpression
        Dim parser As New ExpressionParser(parameters, expressionStr, values)
        Return Expression.Lambda(parser.Parse(resultType), parameters)
    End Function

    Public Function ParseLambda(Of T, S)(ByVal expression As String, ByVal ParamArray values() As Object) As Expression(Of Func(Of T, S))
        Return DirectCast(ParseLambda(GetType(T), GetType(S), expression, values), Expression(Of Func(Of T, S)))
    End Function

    Public Function CreateClass(ByVal ParamArray properties() As DynamicProperty) As Type
        Return ClassFactory.Instance.GetDynamicClass(properties)
    End Function

    Public Function CreateClass(ByVal properties As IEnumerable(Of DynamicProperty)) As Type
        Return ClassFactory.Instance.GetDynamicClass(properties)
    End Function
End Module

Friend Class DynamicOrdering
    Public Selector As Expression
    Public Ascending As Boolean
End Class

Friend Class Signature : Implements IEquatable(Of Signature)
    Public properties() As DynamicProperty
    Public hashCode As Integer

    Public Sub New(ByVal properties As IEnumerable(Of DynamicProperty))
        Me.properties = properties.ToArray()
        hashCode = 0
        For Each p As DynamicProperty In Me.properties
            hashCode = hashCode Xor p.Name.GetHashCode() Xor p.Type.GetHashCode()
        Next p
    End Sub

    Public Overrides Function GetHashCode() As Integer
        Return hashCode
    End Function

    Public Overrides Function Equals(ByVal obj As Object) As Boolean
        Dim cast = TryCast(obj, Signature)
        Return If(cast IsNot Nothing, Equals(cast), False)
    End Function

    Public Overloads Function Equals(ByVal other As Signature) As Boolean Implements IEquatable(Of Signature).Equals
        If (properties.Length <> other.properties.Length) Then Return False
        For i As Integer = 0 To properties.Length - 1
            If (properties(i).Name <> other.properties(i).Name OrElse _
                Not properties(i).Type.Equals(other.properties(i).Type)) Then
                Return False
            End If
        Next i
        Return True
    End Function
End Class

Friend Class ClassFactory
    Public Shared ReadOnly Instance As New ClassFactory()

    Shared Sub New()
        ' Trigger lazy initialization of static fields
    End Sub

    Private [module] As ModuleBuilder
    Private classes As Dictionary(Of Signature, Type)
    Private classCount As Integer
    Private rwLock As ReaderWriterLock

    Private Sub New()
        Dim name As New AssemblyName("DynamicClasses")
        Dim assembly As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run)
#If ENABLE_LINQ_PARTIAL_TRUST Then
            call new ReflectionPermission(PermissionState.Unrestricted).Assert()
#End If
        Try
            [module] = assembly.DefineDynamicModule("Module")
        Finally
#If ENABLE_LINQ_PARTIAL_TRUST Then
                PermissionSet.RevertAssert()
#End If
        End Try
        classes = New Dictionary(Of Signature, Type)()
        rwLock = New ReaderWriterLock()
    End Sub

    Public Function GetDynamicClass(ByVal properties As IEnumerable(Of DynamicProperty)) As Type
        rwLock.AcquireReaderLock(Timeout.Infinite)

        Try
            Dim signature As New Signature(properties)
            Dim type As Type = Nothing
            If Not classes.TryGetValue(signature, type) Then
                type = CreateDynamicClass(signature.properties)
                classes.Add(signature, type)
            End If
            Return type
        Finally
            rwLock.ReleaseReaderLock()
        End Try
    End Function

    Private Function CreateDynamicClass(ByVal properties() As DynamicProperty) As Type
        Dim cookie As LockCookie = rwLock.UpgradeToWriterLock(Timeout.Infinite)
        Try
            Dim typeName = "DynamicClass" & (classCount + 1)
#If ENABLE_LINQ_PARTIAL_TRUST Then
                Call New ReflectionPermission(PermissionState.Unrestricted).Assert()
#End If
            Try
                Dim tb As TypeBuilder = Me.module.DefineType(typeName, TypeAttributes.Class Or _
                                                                       TypeAttributes.Public, GetType(DynamicClass))
                Dim fields() As FieldInfo = GenerateProperties(tb, properties)
                GenerateEquals(tb, fields)
                GenerateGetHashCode(tb, fields)
                Dim result As Type = tb.CreateType()
                classCount += 1
                Return result
            Finally
#If ENABLE_LINQ_PARTIAL_TRUST Then
                    PermissionSet.RevertAssert()
#End If
            End Try
        Finally
            rwLock.DowngradeFromWriterLock(cookie)
        End Try
    End Function

    Private Function GenerateProperties(ByVal tb As TypeBuilder, ByVal properties() As DynamicProperty) As FieldInfo()
        Dim fields(properties.Length - 1) As FieldInfo

        For i As Integer = 0 To properties.Length - 1
            Dim dp As DynamicProperty = properties(i)
            Dim fb As FieldBuilder = tb.DefineField("_" & dp.Name, dp.Type, FieldAttributes.Private)
            Dim pb As PropertyBuilder = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, Nothing)
            Dim mbGet As MethodBuilder = tb.DefineMethod("get_" + dp.Name, _
                                                         MethodAttributes.Public Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, _
                                                         dp.Type, System.Type.EmptyTypes)
            Dim genGet As ILGenerator = mbGet.GetILGenerator()
            genGet.Emit(OpCodes.Ldarg_0)
            genGet.Emit(OpCodes.Ldfld, fb)
            genGet.Emit(OpCodes.Ret)
            Dim mbSet As MethodBuilder = tb.DefineMethod("set_" & dp.Name, _
                                                         MethodAttributes.Public Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, _
                                                         Nothing, New Type() {dp.Type})
            Dim genSet As ILGenerator = mbSet.GetILGenerator()
            genSet.Emit(OpCodes.Ldarg_0)
            genSet.Emit(OpCodes.Ldarg_1)
            genSet.Emit(OpCodes.Stfld, fb)
            genSet.Emit(OpCodes.Ret)
            pb.SetGetMethod(mbGet)
            pb.SetSetMethod(mbSet)
            fields(i) = fb
        Next i

        Return fields
    End Function

    Private Sub GenerateEquals(ByVal tb As TypeBuilder, ByVal fields As FieldInfo())
        Dim mb As MethodBuilder = tb.DefineMethod("Equals", _
                                                  MethodAttributes.Public Or MethodAttributes.ReuseSlot Or _
                                                  MethodAttributes.Virtual Or MethodAttributes.HideBySig, _
                                                  GetType(Boolean), New Type() {GetType(Object)})
        Dim gen As ILGenerator = mb.GetILGenerator()
        Dim other As LocalBuilder = gen.DeclareLocal(tb)
        Dim [next] As Label = gen.DefineLabel()
        gen.Emit(OpCodes.Ldarg_1)
        gen.Emit(OpCodes.Isinst, tb)
        gen.Emit(OpCodes.Stloc, other)
        gen.Emit(OpCodes.Ldloc, other)
        gen.Emit(OpCodes.Brtrue_S, [next])
        gen.Emit(OpCodes.Ldc_I4_0)
        gen.Emit(OpCodes.Ret)
        gen.MarkLabel([next])
        For Each field As FieldInfo In fields
            Dim ft As Type = field.FieldType
            Dim ct As Type = GetType(EqualityComparer(Of Object)).GetGenericTypeDefinition().MakeGenericType(ft)
            [next] = gen.DefineLabel()
            gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), Nothing)
            gen.Emit(OpCodes.Ldarg_0)
            gen.Emit(OpCodes.Ldfld, field)
            gen.Emit(OpCodes.Ldloc, other)
            gen.Emit(OpCodes.Ldfld, field)
            gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", New Type() {ft, ft}), Nothing)
            gen.Emit(OpCodes.Brtrue_S, [next])
            gen.Emit(OpCodes.Ldc_I4_0)
            gen.Emit(OpCodes.Ret)
            gen.MarkLabel([next])
        Next
        gen.Emit(OpCodes.Ldc_I4_1)
        gen.Emit(OpCodes.Ret)
    End Sub

    Private Sub GenerateGetHashCode(ByVal tb As TypeBuilder, ByVal fields As FieldInfo())
        Dim mb As MethodBuilder = tb.DefineMethod("GetHashCode", _
                                                  MethodAttributes.Public Or MethodAttributes.ReuseSlot Or _
                                                  MethodAttributes.Virtual Or MethodAttributes.HideBySig, _
                                                  GetType(Integer), System.Type.EmptyTypes)
        Dim gen As ILGenerator = mb.GetILGenerator()
        gen.Emit(OpCodes.Ldc_I4_0)
        For Each field As FieldInfo In fields
            Dim ft As Type = field.FieldType
            Dim ct As Type = GetType(EqualityComparer(Of Object)).GetGenericTypeDefinition().MakeGenericType(ft)
            gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), Nothing)
            gen.Emit(OpCodes.Ldarg_0)
            gen.Emit(OpCodes.Ldfld, field)
            gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", New Type() {ft}), Nothing)
            gen.Emit(OpCodes.Xor)
        Next
        gen.Emit(OpCodes.Ret)
    End Sub
End Class

Public NotInheritable Class ParseException : Inherits Exception
    Private positionValue As Integer

    Public Sub New(ByVal message As String, ByVal position As Integer)
        MyBase.New(message)
        Me.positionValue = position
    End Sub

    Public ReadOnly Property Position() As Integer
        Get
            Return positionValue
        End Get
    End Property

    Public Overrides Function ToString() As String
        Return String.Format(Res.ParseExceptionFormat, Message, Position)
    End Function
End Class

Class ExpressionParser
    Structure Token
        Public id As TokenId
        Public text As String
        Public pos As Integer
    End Structure

    Enum TokenId
        Unknown
        [End]
        Identifier
        StringLiteral
        IntegerLiteral
        RealLiteral
        Exclamation
        Percent
        Amphersand
        OpenParen
        CloseParen
        Asterisk
        Plus
        Comma
        Minus
        Dot
        Slash
        Colon
        LessThan
        Equal
        GreaterThan
        Question
        OpenBracket
        CloseBracket
        Bar
        ExclamationEqual
        DoubleAmphersand
        LessThanEqual
        LessGreater
        DoubleEqual
        GreaterThanEqual
        DoubleBar
    End Enum

    Interface ILogicalSignatures
        Sub F(ByVal x As Boolean, ByVal y As Boolean)
        Sub F(ByVal x? As Boolean, ByVal y? As Boolean)
    End Interface

    Interface IArithmeticSignatures
        Sub F(ByVal x As Integer, ByVal y As Integer)
        Sub F(ByVal x As UInteger, ByVal y As UInteger)
        Sub F(ByVal x As Long, ByVal y As Long)
        Sub F(ByVal x As ULong, ByVal y As ULong)
        Sub F(ByVal x As Single, ByVal y As Single)
        Sub F(ByVal x As Double, ByVal y As Double)
        Sub F(ByVal x As Decimal, ByVal y As Decimal)
        Sub F(ByVal x? As Integer, ByVal y? As Integer)
        Sub F(ByVal x? As UInteger, ByVal y? As UInteger)
        Sub F(ByVal x? As Long, ByVal y? As Long)
        Sub F(ByVal x? As ULong, ByVal y? As ULong)
        Sub F(ByVal x? As Single, ByVal y? As Single)
        Sub F(ByVal x? As Double, ByVal y? As Double)
        Sub F(ByVal x? As Decimal, ByVal y? As Decimal)
    End Interface

    Interface IRelationalSignatures : Inherits IArithmeticSignatures
        Overloads Sub F(ByVal x As String, ByVal y As String)
        Overloads Sub F(ByVal x As Char, ByVal y As Char)
        Overloads Sub F(ByVal x As DateTime, ByVal y As DateTime)
        Overloads Sub F(ByVal x As TimeSpan, ByVal y As TimeSpan)
        Overloads Sub F(ByVal x? As Char, ByVal y? As Char)
        Overloads Sub F(ByVal x? As DateTime, ByVal y? As DateTime)
        Overloads Sub F(ByVal x? As TimeSpan, ByVal y? As TimeSpan)
    End Interface

    Interface IEqualitySignatures : Inherits IRelationalSignatures
        Overloads Sub F(ByVal x As Boolean, ByVal y As Boolean)
        Overloads Sub F(ByVal x? As Boolean, ByVal y? As Boolean)
    End Interface

    Interface IAddSignatures : Inherits IArithmeticSignatures
        Overloads Sub F(ByVal x As DateTime, ByVal y As TimeSpan)
        Overloads Sub F(ByVal x As TimeSpan, ByVal y As TimeSpan)
        Overloads Sub F(ByVal x? As DateTime, ByVal y? As TimeSpan)
        Overloads Sub F(ByVal x? As TimeSpan, ByVal y? As TimeSpan)
    End Interface

    Interface ISubtractSignatures : Inherits IAddSignatures
        Overloads Sub F(ByVal x As DateTime, ByVal y As DateTime)
        Overloads Sub F(ByVal x? As DateTime, ByVal y? As DateTime)
    End Interface

    Interface INegationSignatures
        Sub F(ByVal x As Integer)
        Sub F(ByVal x As Long)
        Sub F(ByVal x As Single)
        Sub F(ByVal x As Double)
        Sub F(ByVal x As Decimal)
        Sub F(ByVal x As Integer?)
        Sub F(ByVal x As Long?)
        Sub F(ByVal x As Single?)
        Sub F(ByVal x As Double?)
        Sub F(ByVal x As Decimal?)
    End Interface

    Interface INotSignatures
        Sub F(ByVal x As Boolean)
        Sub F(ByVal x? As Boolean)
    End Interface

    Interface IEnumerableSignatures
        Sub Where(ByVal predicate As Boolean)
        Sub Any()
        Sub Any(ByVal predicate As Boolean)
        Sub All(ByVal predicate As Boolean)
        Sub Count()
        Sub Count(ByVal predicate As Boolean)
        Sub Min(ByVal selector As Object)
        Sub Max(ByVal selector As Object)
        Sub Sum(ByVal selector As Integer)
        Sub Sum(ByVal selector? As Integer)
        Sub Sum(ByVal selector As Long)
        Sub Sum(ByVal selector? As Long)
        Sub Sum(ByVal selector As Single)
        Sub Sum(ByVal selector? As Single)
        Sub Sum(ByVal selector As Double)
        Sub Sum(ByVal selector? As Double)
        Sub Sum(ByVal selector As Decimal)
        Sub Sum(ByVal selector? As Decimal)
        Sub Average(ByVal selector As Integer)
        Sub Average(ByVal selector? As Integer)
        Sub Average(ByVal selector As Long)
        Sub Average(ByVal selector? As Long)
        Sub Average(ByVal selector As Single)
        Sub Average(ByVal selector? As Single)
        Sub Average(ByVal selector As Double)
        Sub Average(ByVal selector? As Double)
        Sub Average(ByVal selector As Decimal)
        Sub Average(ByVal selector? As Decimal)
    End Interface

    Shared ReadOnly predefinedTypes As Type() = { _
                                                    GetType(Object), _
                                                    GetType(Boolean), _
                                                    GetType(Char), _
                                                    GetType(String), _
                                                    GetType(SByte), _
                                                    GetType(Byte), _
                                                    GetType(Int16), _
                                                    GetType(UInt16), _
                                                    GetType(Int32), _
                                                    GetType(UInt32), _
                                                    GetType(Int64), _
                                                    GetType(UInt64), _
                                                    GetType(Single), _
                                                    GetType(Double), _
                                                    GetType(Decimal), _
                                                    GetType(DateTime), _
                                                    GetType(TimeSpan), _
                                                    GetType(Guid), _
                                                    GetType(Math), _
                                                    GetType(Convert), _
                                                    GetType(Regex) _
                                                }

    Shared ReadOnly trueLiteral As Expression = Expression.Constant(True)
    Shared ReadOnly falseLiteral As Expression = Expression.Constant(False)
    Shared ReadOnly nullLiteral As Expression = Expression.Constant(Nothing)

    Shared ReadOnly keywordIt As String = "it"
    Shared ReadOnly keywordIif As String = "iif"
    Shared ReadOnly keywordNew As String = "new"

    Shared keywords As Dictionary(Of String, Object)

    Dim symbols As Dictionary(Of String, Object)
    Dim externals As IDictionary(Of String, Object)
    Dim literals As Dictionary(Of Expression, String)
    Dim it As ParameterExpression
    Dim text As String
    Dim textPos As Integer
    Dim textLen As Integer
    Dim ch As Char
    Dim tokenVal As Token

    Public Sub New(ByVal parameters As ParameterExpression(), ByVal expression As String, ByVal values As Object())
        If expression Is Nothing Then Throw New ArgumentNullException("expression")
        If keywords Is Nothing Then keywords = CreateKeywords()
        symbols = New Dictionary(Of String, Object)(StringComparer.OrdinalIgnoreCase)
        literals = New Dictionary(Of Expression, String)()
        If parameters IsNot Nothing Then ProcessParameters(parameters)
        If values IsNot Nothing Then ProcessValues(values)
        text = expression
        textLen = text.Length
        SetTextPos(0)
        NextToken()
    End Sub

    Sub ProcessParameters(ByVal parameters As ParameterExpression())
        For Each pe As ParameterExpression In parameters
            If Not String.IsNullOrEmpty(pe.Name) Then
                AddSymbol(pe.Name, pe)
            End If
        Next

        If (parameters.Length = 1 AndAlso String.IsNullOrEmpty(parameters(0).Name)) Then
            it = parameters(0)
        End If
    End Sub

    Sub ProcessValues(ByVal values As Object())
        For i As Integer = 0 To values.Length - 1
            Dim value As Object = values(i)
            If i = values.Length - 1 AndAlso TryCast(value, IDictionary(Of String, Object)) IsNot Nothing Then
                externals = DirectCast(value, IDictionary(Of String, Object))
            Else
                AddSymbol("@" & i.ToString(System.Globalization.CultureInfo.InvariantCulture), value)
            End If
        Next
    End Sub

    Sub AddSymbol(ByVal name As String, ByVal value As Object)
        If (symbols.ContainsKey(name)) Then
            Throw ParseError(Res.DuplicateIdentifier, name)
        End If
        symbols.Add(name, value)
    End Sub

    Public Function Parse(ByVal resultType As Type) As Expression
        Dim exprPos As Integer = tokenVal.pos
        Dim expr As Expression = ParseExpression()
        If resultType IsNot Nothing Then
            expr = PromoteExpression(expr, resultType, True)
            If expr Is Nothing Then
                Throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType))
            End If
        End If
        ValidateToken(TokenId.End, Res.SyntaxError)
        Return expr
    End Function

    Public Function ParseOrdering() As IEnumerable(Of DynamicOrdering)
        Dim orderings As List(Of DynamicOrdering) = New List(Of DynamicOrdering)()
        Do
            Dim expr As Expression = ParseExpression()
            Dim ascending As Boolean = True
            If TokenIdentifierIs("asc") OrElse TokenIdentifierIs("ascending") Then
                NextToken()
            ElseIf TokenIdentifierIs("desc") OrElse TokenIdentifierIs("descending") Then
                NextToken()
                ascending = False
            End If
            orderings.Add(New DynamicOrdering() With {.Selector = expr, .Ascending = ascending})
            If tokenVal.id <> TokenId.Comma Then Exit Do
            NextToken()
        Loop
        ValidateToken(TokenId.End, Res.SyntaxError)
        Return orderings
    End Function
    '#pragma warning restore 0219

    ' ?: operator
    Function ParseExpression() As Expression
        Dim errorPos As Integer = tokenVal.pos
        Dim expr As Expression = ParseLogicalOr()
        If tokenVal.id = TokenId.Question Then
            NextToken()
            Dim expr1 As Expression = ParseExpression()
            ValidateToken(TokenId.Colon, Res.ColonExpected)
            NextToken()
            Dim expr2 As Expression = ParseExpression()
            expr = GenerateConditional(expr, expr1, expr2, errorPos)
        End If
        Return expr
    End Function

    ' ||, or,orelse operator
    Function ParseLogicalOr() As Expression
        Dim left As Expression = ParseLogicalAnd()
        Do While tokenVal.id = TokenId.DoubleBar OrElse TokenIdentifierIs("or") OrElse TokenIdentifierIs("orelse")
            Dim op As Token = tokenVal
            NextToken()
            Dim right As Expression = ParseLogicalAnd()
            CheckAndPromoteOperands(GetType(ILogicalSignatures), op.text, left, right, op.pos)
            left = Expression.OrElse(left, right)
        Loop
        Return left
    End Function

    ' &&, and, andalso operator
    Function ParseLogicalAnd() As Expression
        Dim left As Expression = ParseComparison()
        Do While tokenVal.id = TokenId.DoubleAmphersand OrElse TokenIdentifierIs("and") OrElse TokenIdentifierIs("andalso")
            Dim op As Token = tokenVal
            NextToken()
            Dim right As Expression = ParseComparison()
            CheckAndPromoteOperands(GetType(ILogicalSignatures), op.text, left, right, op.pos)
            left = Expression.AndAlso(left, right)
        Loop
        Return left
    End Function

    ' =, ==, !=, <>, >, >=, <, <= operators
    Function ParseComparison() As Expression
        Dim left As Expression = ParseAdditive()
        Do While tokenVal.id = TokenId.Equal OrElse tokenVal.id = TokenId.DoubleEqual OrElse _
                 tokenVal.id = TokenId.ExclamationEqual OrElse tokenVal.id = TokenId.LessGreater OrElse _
                 tokenVal.id = TokenId.GreaterThan OrElse tokenVal.id = TokenId.GreaterThanEqual OrElse _
                 tokenVal.id = TokenId.LessThan OrElse tokenVal.id = TokenId.LessThanEqual
            Dim op As Token = tokenVal
            NextToken()
            Dim right As Expression = ParseAdditive()
            Dim isEquality As Boolean = (op.id = TokenId.Equal OrElse op.id = TokenId.DoubleEqual OrElse _
                                         op.id = TokenId.ExclamationEqual OrElse op.id = TokenId.LessGreater)
            If isEquality AndAlso Not left.Type.IsValueType AndAlso Not right.Type.IsValueType Then
                If Not left.Type.Equals(right.Type) Then
                    If left.Type.IsAssignableFrom(right.Type) Then
                        right = Expression.Convert(right, left.Type)
                    ElseIf right.Type.IsAssignableFrom(left.Type) Then
                        left = Expression.Convert(left, right.Type)
                    Else
                        Throw IncompatibleOperandsError(op.text, left, right, op.pos)
                    End If
                End If
            ElseIf IsEnumType(left.Type) OrElse IsEnumType(right.Type) Then
                If Not left.Type.Equals(right.Type) Then
                    Dim e As Expression = PromoteExpression(right, left.Type, True)
                    If e IsNot Nothing Then
                        right = e
                    Else
                        e = PromoteExpression(left, right.Type, True)
                        If e Is Nothing Then
                            Throw IncompatibleOperandsError(op.text, left, right, op.pos)
                        End If
                        left = e
                    End If
                End If
            Else
                CheckAndPromoteOperands(If(isEquality, GetType(IEqualitySignatures), GetType(IRelationalSignatures)), _
                                        op.text, left, right, op.pos)
            End If
            Select Case op.id
                Case TokenId.Equal, TokenId.DoubleEqual
                    left = GenerateEqual(left, right)
                Case TokenId.ExclamationEqual, TokenId.LessGreater
                    left = GenerateNotEqual(left, right)
                Case TokenId.GreaterThan
                    left = GenerateGreaterThan(left, right)
                Case TokenId.GreaterThanEqual
                    left = GenerateGreaterThanEqual(left, right)
                Case TokenId.LessThan
                    left = GenerateLessThan(left, right)
                Case TokenId.LessThanEqual
                    left = GenerateLessThanEqual(left, right)
            End Select
        Loop
        Return left
    End Function

    ' +, -, & operators
    Function ParseAdditive() As Expression
        Dim left = ParseMultiplicative()
        Do While tokenVal.id = TokenId.Plus OrElse tokenVal.id = TokenId.Minus OrElse _
                 tokenVal.id = TokenId.Amphersand
            Dim op = tokenVal
            NextToken()
            Dim right = ParseMultiplicative()
            Select Case op.id
                Case TokenId.Plus
                    If left.Type.Equals(GetType(String)) OrElse right.Type.Equals(GetType(String)) Then
                        GoTo amphersand
                    End If
                    CheckAndPromoteOperands(GetType(IAddSignatures), op.text, left, right, op.pos)
                    left = GenerateAdd(left, right)
                Case TokenId.Minus
                    CheckAndPromoteOperands(GetType(ISubtractSignatures), op.text, left, right, op.pos)
                    left = GenerateSubtract(left, right)
                Case TokenId.Amphersand
amphersand:
                    left = GenerateStringConcat(left, right)
            End Select
        Loop
        Return left
    End Function

    ' *, /, %, mod operators
    Function ParseMultiplicative() As Expression
        Dim left = ParseUnary()
        Do While tokenVal.id = TokenId.Asterisk OrElse tokenVal.id = TokenId.Slash OrElse _
                 tokenVal.id = TokenId.Percent OrElse TokenIdentifierIs("mod")
            Dim op = tokenVal
            NextToken()
            Dim right = ParseUnary()
            CheckAndPromoteOperands(GetType(IArithmeticSignatures), op.text, left, right, op.pos)
            Select Case op.id
                Case TokenId.Asterisk
                    left = Expression.Multiply(left, right)
                Case TokenId.Slash
                    left = Expression.Divide(left, right)
                Case TokenId.Percent, TokenId.Identifier
                    left = Expression.Modulo(left, right)
            End Select
        Loop
        Return left
    End Function

    ' -, !, not unary operators
    Function ParseUnary() As Expression
        If tokenVal.id = TokenId.Minus OrElse tokenVal.id = TokenId.Exclamation OrElse _
           TokenIdentifierIs("not") Then

            Dim op = tokenVal
            NextToken()
            If op.id = TokenId.Minus AndAlso (tokenVal.id = TokenId.IntegerLiteral OrElse _
                                              tokenVal.id = TokenId.RealLiteral) Then
                tokenVal.text = "-" & tokenVal.text
                tokenVal.pos = op.pos
                Return ParsePrimary()
            End If
            Dim expr = ParseUnary()
            If op.id = TokenId.Minus Then
                CheckAndPromoteOperand(GetType(INegationSignatures), op.text, expr, op.pos)
                expr = Expression.Negate(expr)
            Else
                CheckAndPromoteOperand(GetType(INotSignatures), op.text, expr, op.pos)
                expr = Expression.Not(expr)
            End If
            Return expr
        End If
        Return ParsePrimary()
    End Function

    Function ParsePrimary() As Expression
        Dim expr = ParsePrimaryStart()
        Do
            If tokenVal.id = TokenId.Dot Then
                NextToken()
                expr = ParseMemberAccess(Nothing, expr)
            ElseIf tokenVal.id = TokenId.OpenBracket Then
                expr = ParseElementAccess(expr)
            Else
                Exit Do
            End If
        Loop
        Return expr
    End Function

    Function ParsePrimaryStart() As Expression
        Select Case tokenVal.id
            Case TokenId.Identifier
                Return ParseIdentifier()
            Case TokenId.StringLiteral
                Return ParseStringLiteral()
            Case TokenId.IntegerLiteral
                Return ParseIntegerLiteral()
            Case TokenId.RealLiteral
                Return ParseRealLiteral()
            Case TokenId.OpenParen
                Return ParseParenExpression()
            Case Else
                Throw ParseError(Res.ExpressionExpected)
        End Select
    End Function

    Function ParseStringLiteral() As Expression
        ValidateToken(TokenId.StringLiteral)

        Dim quote = tokenVal.text(0)
        Dim s = tokenVal.text.Substring(1, tokenVal.text.Length - 2)
        Dim start = 0

        Do
            Dim i = s.IndexOf(quote, start)
            If i < 0 Then Exit Do
            s = s.Remove(i, 1)
            start = i + 1
        Loop

        If quote = "'" Then
            If s.Length <> 1 Then
                Throw ParseError(Res.InvalidCharacterLiteral)
            End If
            NextToken()
            Return CreateLiteral(s(0), s)
        End If
        NextToken()
        Return CreateLiteral(s, s)
    End Function

    Function ParseIntegerLiteral() As Expression
        ValidateToken(TokenId.IntegerLiteral)
        Dim text = tokenVal.text
        If text(0) <> "-" Then
            Dim value As ULong = 0
            If Not UInt64.TryParse(text, value) Then
                Throw ParseError(Res.InvalidIntegerLiteral, text)
            End If

            NextToken()
            If value <= CULng(Int32.MaxValue) Then Return CreateLiteral(CInt(value), text)
            If value <= CULng(UInt32.MaxValue) Then Return CreateLiteral(CUInt(value), text)
            If value <= CULng(Int64.MaxValue) Then Return CreateLiteral(CLng(value), text)
            Return CreateLiteral(value, text)
        Else
            Dim value As Long = 0
            If Not Int64.TryParse(text, value) Then
                Throw ParseError(Res.InvalidIntegerLiteral, text)
            End If
            NextToken()
            If (value >= Int32.MinValue AndAlso value <= Int32.MaxValue) Then
                Return CreateLiteral(CInt(value), text)
            End If
            Return CreateLiteral(value, text)
        End If
    End Function

    Function ParseRealLiteral() As Expression
        ValidateToken(TokenId.RealLiteral)
        Dim text = tokenVal.text
        Dim value As Object = Nothing
        Dim last = text(text.Length - 1)
        If last = "f" Or last = "F" Then
            Dim f As Single
            If Single.TryParse(text.Substring(0, text.Length - 1), f) Then value = f

        Else
            Dim d As Double
            If Double.TryParse(text, d) Then value = d
        End If

        If value Is Nothing Then Throw ParseError(Res.InvalidRealLiteral, text)
        NextToken()
        Return CreateLiteral(value, text)
    End Function

    Function CreateLiteral(ByVal value As Object, ByVal text As String) As Expression
        Dim expr = Expression.Constant(value)
        literals.Add(expr, text)
        Return expr
    End Function

    Function ParseParenExpression() As Expression
        ValidateToken(TokenId.OpenParen, Res.OpenParenExpected)
        NextToken()
        Dim e = ParseExpression()
        ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected)
        NextToken()
        Return e
    End Function

    Function ParseIdentifier() As Expression
        ValidateToken(TokenId.Identifier)
        Dim value As Object = Nothing
        If keywords.TryGetValue(tokenVal.text, value) Then
            If TryCast(value, Type) IsNot Nothing Then Return ParseTypeAccess(DirectCast(value, Type))
            If value Is keywordIt Then Return ParseIt()
            If value Is keywordIif Then Return ParseIif()
            If value Is keywordNew Then Return ParseNew()
            NextToken()
            Return DirectCast(value, Expression)
        End If

        If symbols.TryGetValue(tokenVal.text, value) OrElse _
           externals IsNot Nothing AndAlso externals.TryGetValue(tokenVal.text, value) Then
            Dim expr = TryCast(value, Expression)
            If expr Is Nothing Then
                expr = Expression.Constant(value)
            Else
                Dim lambda = TryCast(expr, LambdaExpression)
                If lambda IsNot Nothing Then Return ParseLambdaInvocation(lambda)
            End If
            NextToken()
            Return expr
        End If
        If it IsNot Nothing Then Return ParseMemberAccess(Nothing, it)
        Throw ParseError(Res.UnknownIdentifier, tokenVal.text)
    End Function

    Function ParseIt() As Expression
        If it Is Nothing Then Throw ParseError(Res.NoItInScope)
        NextToken()
        Return it
    End Function

    Function ParseIif() As Expression
        Dim errorPos = tokenVal.pos
        NextToken()
        Dim args As Expression() = ParseArgumentList()
        If args.Length <> 3 Then
            Throw ParseError(errorPos, Res.IifRequiresThreeArgs)
        End If
        Return GenerateConditional(args(0), args(1), args(2), errorPos)
    End Function

    Function GenerateConditional(ByVal test As Expression, ByVal expr1 As Expression, ByVal expr2 As Expression, ByVal errorPos As Integer) As Expression
        If Not test.Type.Equals(GetType(Boolean)) Then
            Throw ParseError(errorPos, Res.FirstExprMustBeBool)
        End If
        If Not expr1.Type.Equals(expr2.Type) Then
            Dim expr1as2 As Expression = If(Not expr2.Equals(nullLiteral), PromoteExpression(expr1, expr2.Type, True), Nothing)
            Dim expr2as1 As Expression = If(Not expr1.Equals(nullLiteral), PromoteExpression(expr2, expr1.Type, True), Nothing)
            If expr1as2 IsNot Nothing And expr2as1 Is Nothing Then
                expr1 = expr1as2
            ElseIf expr2as1 IsNot Nothing And expr1as2 Is Nothing Then
                expr2 = expr2as1
            Else
                Dim type1 = If(Not expr1.Equals(nullLiteral), expr1.Type.Name, "null")
                Dim type2 = If(Not expr2.Equals(nullLiteral), expr2.Type.Name, "null")
                If expr1as2 IsNot Nothing And expr2as1 IsNot Nothing Then
                    Throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2)
                End If
                Throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2)
            End If
        End If
        Return Expression.Condition(test, expr1, expr2)
    End Function

    Function ParseNew() As Expression
        NextToken()
        ValidateToken(TokenId.OpenParen, Res.OpenParenExpected)
        NextToken()
        Dim properties As New List(Of DynamicProperty)()
        Dim expressions As New List(Of Expression)()
        Do
            Dim exprPos = tokenVal.pos
            Dim expr = ParseExpression()
            Dim propName As String
            If TokenIdentifierIs("as") Then
                NextToken()
                propName = GetIdentifier()
                NextToken()
            Else
                Dim [me] As MemberExpression = TryCast(expr, MemberExpression)
                If [me] Is Nothing Then Throw ParseError(exprPos, Res.MissingAsClause)
                propName = [me].Member.Name
            End If
            expressions.Add(expr)
            properties.Add(New DynamicProperty(propName, expr.Type))
            If tokenVal.id <> TokenId.Comma Then Exit Do
            NextToken()
        Loop
        ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected)
        NextToken()
        Dim type As Type = DynamicExpression.CreateClass(properties)
        Dim bindings(properties.Count - 1) As MemberBinding
        For i As Integer = 0 To bindings.Length - 1
            bindings(i) = Expression.Bind(type.GetProperty(properties(i).Name), expressions(i))
        Next
        Return Expression.MemberInit(Expression.[New](type), bindings)
    End Function

    Function ParseLambdaInvocation(ByVal lambda As LambdaExpression) As Expression
        Dim errorPos = tokenVal.pos
        NextToken()
        Dim args As Expression() = ParseArgumentList()
        Dim method As MethodBase = Nothing
        If FindMethod(lambda.Type, "Invoke", False, args, method) <> 1 Then
            Throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda)
        End If
        Return Expression.Invoke(lambda, args)
    End Function

    Function ParseTypeAccess(ByVal type As Type) As Expression
        Dim errorPos = tokenVal.pos
        NextToken()

        If tokenVal.id = TokenId.Question Then
            If (Not type.IsValueType) OrElse IsNullableType(type) Then
                Throw ParseError(errorPos, Res.TypeHasNoNullableForm, GetTypeName(type))
            End If
            type = GetType(Nullable(Of Integer)).GetGenericTypeDefinition().MakeGenericType(type)
            NextToken()
        End If
        If tokenVal.id = TokenId.OpenParen Then
            Dim args As Expression() = ParseArgumentList()
            Dim method As MethodBase = Nothing
            Select Case FindBestMethod(type.GetConstructors(), args, method)
                Case 0
                    If args.Length = 1 Then
                        Return GenerateConversion(args(0), type, errorPos)
                    End If
                    Throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type))
                Case 1
                    Return Expression.[New](DirectCast(method, ConstructorInfo), args)
                Case Else
                    Throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type))
            End Select
        End If
        ValidateToken(TokenId.Dot, Res.DotOrOpenParenExpected)
        NextToken()
        Return ParseMemberAccess(type, Nothing)
    End Function

    Function GenerateConversion(ByVal expr As Expression, ByVal type As Type, ByVal errorPos As Integer) As Expression
        Dim exprType = expr.Type
        If exprType.Equals(type) Then Return expr
        If exprType.IsValueType AndAlso type.IsValueType Then
            If (IsNullableType(exprType) OrElse IsNullableType(type)) AndAlso _
               GetNonNullableType(exprType).Equals(GetNonNullableType(type)) Then

                Return Expression.Convert(expr, type)
            End If
            If (IsNumericType(exprType) OrElse IsEnumType(exprType)) AndAlso _
               (IsNumericType(type) OrElse IsEnumType(type)) Then

                Return Expression.ConvertChecked(expr, type)
            End If
        End If
        If exprType.IsAssignableFrom(type) OrElse type.IsAssignableFrom(exprType) OrElse _
           exprType.IsInterface OrElse type.IsInterface Then
            Return Expression.Convert(expr, type)
        End If
        Throw ParseError(errorPos, Res.CannotConvertValue, _
                         GetTypeName(exprType), GetTypeName(type))
    End Function


    Function ParseMemberAccess(ByVal type As Type, ByVal instance As Expression) As Expression
        If instance IsNot Nothing Then type = instance.Type
        Dim errorPos = tokenVal.pos
        Dim id = GetIdentifier()
        NextToken()
        If tokenVal.id = TokenId.OpenParen Then
            If instance IsNot Nothing AndAlso Not type.Equals(GetType(String)) Then
                Dim enumerableType As Type = FindGenericType(GetType(IEnumerable(Of Object)).GetGenericTypeDefinition(), type)
                If enumerableType IsNot Nothing Then
                    Dim elementType As Type = enumerableType.GetGenericArguments()(0)
                    Return ParseAggregate(instance, elementType, id, errorPos)
                End If
            End If
            Dim args As Expression() = ParseArgumentList()
            Dim mb As MethodBase = Nothing
            Select Case FindMethod(type, id, instance Is Nothing, args, mb)
                Case 0
                    Throw ParseError(errorPos, Res.NoApplicableMethod, id, GetTypeName(type))
                Case 1
                    Dim method = DirectCast(mb, MethodInfo)
                    If (Not IsPredefinedType(method.DeclaringType)) Then
                        Throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType))
                    End If
                    If method.ReturnType.Equals(GetType(Void)) Then
                        Throw ParseError(errorPos, Res.MethodIsVoid, id, GetTypeName(method.DeclaringType))
                    End If
                    Return Expression.Call(instance, DirectCast(method, MethodInfo), args)
                Case Else
                    Throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, GetTypeName(type))
            End Select
        Else
            Dim member As MemberInfo = FindPropertyOrField(type, id, instance Is Nothing)
            If member Is Nothing Then
                Throw ParseError(errorPos, Res.UnknownPropertyOrField, id, GetTypeName(type))
            End If
            Return If(TryCast(member, PropertyInfo) IsNot Nothing, _
                      Expression.Property(instance, DirectCast(member, PropertyInfo)), _
                      Expression.Field(instance, DirectCast(member, FieldInfo)))
        End If
    End Function

    Shared Function FindGenericType(ByVal generic As Type, ByVal type As Type) As Type
        Do While type IsNot Nothing AndAlso Not type.Equals(GetType(Object))
            If type.IsGenericType AndAlso type.GetGenericTypeDefinition().Equals(generic) Then Return type
            If generic.IsInterface Then
                For Each intfType As Type In type.GetInterfaces()
                    Dim found As Type = FindGenericType(generic, intfType)
                    If found IsNot Nothing Then Return found
                Next
            End If
            type = type.BaseType
        Loop
        Return Nothing
    End Function

    Function ParseAggregate(ByVal instance As Expression, ByVal elementType As Type, ByVal methodName As String, ByVal errorPos As Integer) As Expression
        Dim outerIt As ParameterExpression = it
        Dim innerIt As ParameterExpression = Expression.Parameter(elementType, "")
        it = innerIt
        Dim args As Expression() = ParseArgumentList()
        it = outerIt
        Dim signature As MethodBase = Nothing
        If FindMethod(GetType(IEnumerableSignatures), methodName, False, args, signature) <> 1 Then
            Throw ParseError(errorPos, Res.NoApplicableAggregate, methodName)
        End If
        Dim typeArgs As Type()
        If signature.Name = "Min" OrElse signature.Name = "Max" Then
            typeArgs = New Type() {elementType, args(0).Type}
        Else
            typeArgs = New Type() {elementType}
        End If

        If args.Length = 0 Then
            args = New Expression() {instance}
        Else
            args = New Expression() {instance, Expression.Lambda(args(0), innerIt)}
        End If
        Return Expression.Call(GetType(Enumerable), signature.Name, typeArgs, args)
    End Function

    Function ParseArgumentList() As Expression()
        ValidateToken(TokenId.OpenParen, Res.OpenParenExpected)
        NextToken()
        Dim args As Expression() = If(tokenVal.id <> TokenId.CloseParen, ParseArguments(), New Expression(-1) {})
        ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected)
        NextToken()
        Return args
    End Function

    Function ParseArguments() As Expression()
        Dim argList As New List(Of Expression)()
        Do
            argList.Add(ParseExpression())
            If tokenVal.id <> TokenId.Comma Then Exit Do
            NextToken()
        Loop
        Return argList.ToArray()
    End Function

    Function ParseElementAccess(ByVal expr As Expression) As Expression
        Dim errorPos As Integer = tokenVal.pos
        ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected)
        NextToken()
        Dim args As Expression() = ParseArguments()
        ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected)
        NextToken()
        If expr.Type.IsArray Then
            If expr.Type.GetArrayRank() <> 1 OrElse args.Length <> 1 Then
                Throw ParseError(errorPos, Res.CannotIndexMultiDimArray)
            End If
            Dim index As Expression = PromoteExpression(args(0), GetType(Integer), True)
            If index Is Nothing Then
                Throw ParseError(errorPos, Res.InvalidIndex)
            End If
            Return Expression.ArrayIndex(expr, index)
        Else
            Dim mb As MethodBase = Nothing
            Select Case FindIndexer(expr.Type, args, mb)
                Case 0
                    Throw ParseError(errorPos, Res.NoApplicableIndexer, GetTypeName(expr.Type))
                Case 1
                    Return Expression.Call(expr, DirectCast(mb, MethodInfo), args)
                Case Else
                    Throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, GetTypeName(expr.Type))
            End Select
        End If
    End Function

    Shared Function IsPredefinedType(ByVal type As Type) As Boolean
        For Each t As Type In predefinedTypes
            If t.Equals(type) Then Return True
        Next

        Return False
    End Function

    Shared Function IsNullableType(ByVal type As Type) As Boolean
        Return type.IsGenericType AndAlso type.GetGenericTypeDefinition().Equals(GetType(Nullable(Of Integer)).GetGenericTypeDefinition())
    End Function

    Shared Function GetNonNullableType(ByVal type As Type) As Type
        Return If(IsNullableType(type), type.GetGenericArguments()(0), type)
    End Function

    Shared Function GetTypeName(ByVal type As Type) As String
        Dim baseType = GetNonNullableType(type)
        Dim s = baseType.Name
        If Not type.Equals(baseType) Then s &= "?"
        Return s
    End Function

    Shared Function IsNumericType(ByVal type As Type) As Boolean
        Return GetNumericTypeKind(type) <> 0
    End Function

    Shared Function IsSignedIntegralType(ByVal type As Type) As Boolean
        Return GetNumericTypeKind(type) = 2
    End Function

    Shared Function IsUnsignedIntegralType(ByVal type As Type) As Boolean
        Return GetNumericTypeKind(type) = 3
    End Function

    Shared Function GetNumericTypeKind(ByVal type As Type) As Integer
        type = GetNonNullableType(type)
        If type.IsEnum Then Return 0
        Select Case type.GetTypeCode(type)
            Case TypeCode.Char, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                Return 1
            Case TypeCode.SByte, TypeCode.Int16, TypeCode.Int32, TypeCode.Int64
                Return 2
            Case TypeCode.Byte, TypeCode.UInt16, TypeCode.UInt32, TypeCode.UInt64
                Return 3
            Case Else
                Return 0
        End Select
    End Function

    Shared Function IsEnumType(ByVal type As Type) As Boolean
        Return GetNonNullableType(type).IsEnum
    End Function

    Sub CheckAndPromoteOperand(ByVal signatures As Type, ByVal opName As String, ByRef expr As Expression, ByVal errorPos As Integer)
        Dim args As Expression() = New Expression() {expr}
        Dim method As MethodBase = Nothing
        If FindMethod(signatures, "F", False, args, method) <> 1 Then
            Throw ParseError(errorPos, Res.IncompatibleOperand, opName, GetTypeName(args(0).Type))
        End If
        expr = args(0)
    End Sub

    Sub CheckAndPromoteOperands(ByVal signatures As Type, ByVal opName As String, ByRef left As Expression, ByRef right As Expression, ByVal errorPos As Integer)
        Dim args As Expression() = New Expression() {left, right}
        Dim method As MethodBase = Nothing
        If FindMethod(signatures, "F", False, args, method) <> 1 Then
            Throw IncompatibleOperandsError(opName, left, right, errorPos)
        End If
        left = args(0)
        right = args(1)
    End Sub

    Function IncompatibleOperandsError(ByVal opName As String, ByVal left As Expression, ByVal right As Expression, ByVal pos As Integer) As Exception
        Return ParseError(pos, Res.IncompatibleOperands, opName, GetTypeName(left.Type), GetTypeName(right.Type))
    End Function

    Function FindPropertyOrField(ByVal type As Type, ByVal memberName As String, ByVal staticAccess As Boolean) As MemberInfo
        Dim flags As BindingFlags = BindingFlags.Public Or BindingFlags.DeclaredOnly Or _
                                    If(staticAccess, BindingFlags.Static, BindingFlags.Instance)
        For Each t As Type In SelfAndBaseTypes(type)
            Dim members As MemberInfo() = t.FindMembers(MemberTypes.Property Or MemberTypes.Field, _
                                                        flags, type.FilterNameIgnoreCase, memberName)
            If members.Length <> 0 Then Return members(0)
        Next
        Return Nothing
    End Function

    Function FindMethod(ByVal type As Type, ByVal methodName As String, ByVal staticAccess As Boolean, ByVal args As Expression(), ByRef method As MethodBase) As Integer
        Dim flags As BindingFlags = BindingFlags.Public Or BindingFlags.DeclaredOnly Or _
                                    If(staticAccess, BindingFlags.Static, BindingFlags.Instance)
        For Each t As Type In SelfAndBaseTypes(type)
            Dim members As MemberInfo() = t.FindMembers(MemberTypes.Method, _
                                                        flags, type.FilterNameIgnoreCase, methodName)
            Dim count As Integer = FindBestMethod(members.Cast(Of MethodBase)(), args, method)
            If count <> 0 Then Return count
        Next
        method = Nothing
        Return 0
    End Function

    Function FindIndexer(ByVal type As Type, ByVal args As Expression(), ByRef method As MethodBase) As Integer
        For Each t As Type In SelfAndBaseTypes(type)
            Dim members As MemberInfo() = t.GetDefaultMembers()
            If members.Length <> 0 Then
                Dim methods As IEnumerable(Of MethodBase) = members. _
                        OfType(Of PropertyInfo)(). _
                        Select(Function(p) DirectCast(p.GetGetMethod(), MethodBase)). _
                        Where(Function(m) m IsNot Nothing)
                Dim count As Integer = FindBestMethod(methods, args, method)
                If count <> 0 Then Return count
            End If
        Next
        method = Nothing
        Return 0
    End Function

    Shared Function SelfAndBaseTypes(ByVal type As Type) As IEnumerable(Of Type)
        If type.IsInterface Then
            Dim types As New List(Of Type)()
            AddInterface(types, type)
            Return types
        End If
        Return SelfAndBaseClasses(type)
    End Function

    Shared Function SelfAndBaseClasses(ByVal type As Type) As IEnumerable(Of Type)
        Dim results As New LinkedList(Of Type)()

        Do While type IsNot Nothing
            results.AddLast(type)
            type = type.BaseType
        Loop

        Return results
    End Function

    Shared Sub AddInterface(ByVal types As List(Of Type), ByVal type As Type)
        If Not types.Contains(type) Then
            types.Add(type)
        End If
        For Each t As Type In type.GetInterfaces()
            AddInterface(types, t)
        Next
    End Sub

    Class MethodData
        Public MethodBase As MethodBase
        Public Parameters As ParameterInfo()
        Public Args As Expression()
    End Class

    Function FindBestMethod(ByVal methods As IEnumerable(Of MethodBase), ByVal args As Expression(), ByRef method As MethodBase) As Integer
        Dim applicable As MethodData() = methods. _
                Select(Function(m) New MethodData With {.MethodBase = m, .Parameters = m.GetParameters()}). _
                Where(Function(m) IsApplicable(m, args)). _
                ToArray()
        If applicable.Length > 1 Then
            applicable = applicable. _
                Where(Function(m) applicable.All(Function(n) m Is n OrElse IsBetterThan(args, m, n))). _
                ToArray()
        End If
        If applicable.Length = 1 Then
            Dim md As MethodData = applicable(0)
            For i As Integer = 0 To args.Length - 1
                args(i) = md.Args(i)
            Next
            method = md.MethodBase
        Else
            method = Nothing
        End If
        Return applicable.Length
    End Function

    Function IsApplicable(ByVal method As MethodData, ByVal args As Expression()) As Boolean
        If method.Parameters.Length <> args.Length Then Return False
        Dim promotedArgs As Expression() = New Expression(args.Length - 1) {}

        For i As Integer = 0 To args.Length - 1
            Dim pi As ParameterInfo = method.Parameters(i)
            If pi.IsOut Then Return False
            Dim promoted As Expression = PromoteExpression(args(i), pi.ParameterType, False)
            If promoted Is Nothing Then Return False
            promotedArgs(i) = promoted
        Next i
        method.Args = promotedArgs

        Return True
    End Function

    Function PromoteExpression(ByVal expr As Expression, ByVal type As Type, ByVal exact As Boolean) As Expression
        If expr.Type.Equals(type) Then Return expr
        If TryCast(expr, ConstantExpression) IsNot Nothing Then
            Dim ce = DirectCast(expr, ConstantExpression)
            If ce.Equals(nullLiteral) Then
                If Not type.IsValueType OrElse IsNullableType(type) Then
                    Return Expression.Constant(Nothing, type)
                End If
            Else
                Dim text As String = Nothing
                If literals.TryGetValue(ce, text) Then
                    Dim target As Type = GetNonNullableType(type)
                    Dim value As Object = Nothing
                    Select Case type.GetTypeCode(ce.Type)
                        Case TypeCode.Int32, TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64
                            value = ParseNumber(text, target)
                        Case TypeCode.Double
                            If target.Equals(GetType(Decimal)) Then value = ParseNumber(text, target)
                        Case TypeCode.String
                            value = ParseEnum(text, target)
                    End Select
                    Try
                        If value IsNot Nothing Then Return Expression.Constant(value, type)
                    Catch ex As Exception
                        Throw ParseError(Res.ExpressionExpected)
                    End Try
                End If
            End If
        End If

        If IsCompatibleWith(expr.Type, type) Then
            If type.IsValueType OrElse exact Then Return Expression.Convert(expr, type)
            Return expr
        End If
        Return Nothing
    End Function

    Shared Function ParseNumber(ByVal text As String, ByVal type As Type) As Object
        Select Case type.GetTypeCode(GetNonNullableType(type))
            Case TypeCode.SByte
                Dim sb As SByte
                If SByte.TryParse(text, sb) Then Return sb
            Case TypeCode.Byte
                Dim b As Byte
                If Byte.TryParse(text, b) Then Return b
            Case TypeCode.Int16
                Dim s As Short
                If Short.TryParse(text, s) Then Return s
            Case TypeCode.UInt16
                Dim us As UShort
                If UShort.TryParse(text, us) Then Return us
            Case TypeCode.Int32
                Dim i As Integer
                If Integer.TryParse(text, i) Then Return i
            Case TypeCode.UInt32
                Dim ui As UInteger
                If UInteger.TryParse(text, ui) Then Return ui
            Case TypeCode.Int64
                Dim l As Long
                If Long.TryParse(text, l) Then Return l
            Case TypeCode.UInt64
                Dim ul As ULong
                If ULong.TryParse(text, ul) Then Return ul
            Case TypeCode.Single
                Dim f As Single
                If Single.TryParse(text, f) Then Return f
            Case TypeCode.Double
                Dim d As Double
                If Double.TryParse(text, d) Then Return d
            Case TypeCode.Decimal
                Dim e As Decimal
                If Decimal.TryParse(text, e) Then Return e
        End Select
        Return Nothing
    End Function

    Shared Function ParseEnum(ByVal name As String, ByVal type As Type) As Object
        If type.IsEnum Then
            Dim memberInfos As MemberInfo() = type.FindMembers(MemberTypes.Field, _
                                                               BindingFlags.Public Or BindingFlags.DeclaredOnly Or BindingFlags.Static, _
                                                               type.FilterNameIgnoreCase, name)
            If memberInfos.Length <> 0 Then Return DirectCast(memberInfos(0), FieldInfo).GetValue(Nothing)
        End If
        Return Nothing
    End Function

    Shared Function IsCompatibleWith(ByVal source As Type, ByVal target As Type) As Boolean
        If source.Equals(target) Then Return True
        If Not target.IsValueType Then Return target.IsAssignableFrom(source)
        Dim st As Type = GetNonNullableType(source)
        Dim tt As Type = GetNonNullableType(target)
        If Not st.Equals(source) AndAlso tt.Equals(target) Then Return False
        Dim sc As TypeCode = If(st.IsEnum, TypeCode.Object, System.Type.GetTypeCode(st))
        Dim tc As TypeCode = If(tt.IsEnum, TypeCode.Object, System.Type.GetTypeCode(tt))

        Select Case sc
            Case TypeCode.SByte
                Select Case tc
                    Case TypeCode.SByte, TypeCode.Int16, TypeCode.Int32, TypeCode.Int64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.Byte
                Select Case tc
                    Case TypeCode.Byte, TypeCode.Int16, TypeCode.UInt16, TypeCode.Int32, TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.Int16
                Select Case tc
                    Case TypeCode.Int16, TypeCode.Int32, TypeCode.Int64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.UInt16
                Select Case tc
                    Case TypeCode.UInt16, TypeCode.Int32, TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.Int32
                Select Case tc
                    Case TypeCode.Int32, TypeCode.Int64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.UInt32
                Select Case tc
                    Case TypeCode.UInt32, TypeCode.Int64, TypeCode.UInt64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.Int64
                Select Case tc
                    Case TypeCode.Int64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.UInt64
                Select Case tc
                    Case TypeCode.UInt64, TypeCode.Single, TypeCode.Double, TypeCode.Decimal
                        Return True
                End Select
            Case TypeCode.Single
                Select Case tc
                    Case TypeCode.Single, TypeCode.Double
                        Return True
                End Select
            Case Else
                If st.Equals(tt) Then Return True
        End Select
        Return False
    End Function

    Shared Function IsBetterThan(ByVal args As Expression(), ByVal m1 As MethodData, ByVal m2 As MethodData) As Boolean
        Dim better = False
        For i As Integer = 0 To args.Length - 1
            Dim c As Integer = CompareConversions(args(i).Type, _
                                                  m1.Parameters(i).ParameterType, _
                                                  m2.Parameters(i).ParameterType)
            If c < 0 Then Return False
            If c > 0 Then better = True
        Next i
        Return better
    End Function

    ' Return 1 if s -> t1 is a better conversion than s -> t2
    ' Return -1 if s -> t2 is a better conversion than s -> t1
    ' Return 0 if neither conversion is better
    Shared Function CompareConversions(ByVal s As Type, ByVal t1 As Type, ByVal t2 As Type) As Integer
        If t1.Equals(t2) Then Return 0
        If s.Equals(t1) Then Return 1
        If s.Equals(t2) Then Return -1
        Dim t1t2 As Boolean = IsCompatibleWith(t1, t2)
        Dim t2t1 As Boolean = IsCompatibleWith(t2, t1)
        If t1t2 AndAlso Not t2t1 Then Return 1
        If t2t1 AndAlso Not t1t2 Then Return -1
        If IsSignedIntegralType(t1) AndAlso IsUnsignedIntegralType(t2) Then Return 1
        If IsSignedIntegralType(t2) AndAlso IsUnsignedIntegralType(t1) Then Return -1
        Return 0
    End Function

    Function GenerateEqual(ByVal left As Expression, ByVal right As Expression) As Expression
        Return Expression.Equal(left, right)
    End Function

    Function GenerateNotEqual(ByVal left As Expression, ByVal right As Expression) As Expression
        Return Expression.NotEqual(left, right)
    End Function

    Function GenerateGreaterThan(ByVal left As Expression, ByVal right As Expression) As Expression
        If left.Type.Equals(GetType(String)) Then
            Return Expression.GreaterThan( _
                                          GenerateStaticMethodCall("Compare", left, right), _
                                          Expression.Constant(0))
        End If
        Return Expression.GreaterThan(left, right)
    End Function

    Function GenerateGreaterThanEqual(ByVal left As Expression, ByVal right As Expression) As Expression
        If left.Type.Equals(GetType(String)) Then
            Return Expression.GreaterThanOrEqual( _
                                                 GenerateStaticMethodCall("Compare", left, right), _
                                                 Expression.Constant(0))
        End If
        Return Expression.GreaterThanOrEqual(left, right)
    End Function

    Function GenerateLessThan(ByVal left As Expression, ByVal right As Expression) As Expression
        If left.Type.Equals(GetType(String)) Then
            Return Expression.LessThan( _
                                       GenerateStaticMethodCall("Compare", left, right), _
                                       Expression.Constant(0))
        End If
        Return Expression.LessThan(left, right)
    End Function

    Function GenerateLessThanEqual(ByVal left As Expression, ByVal right As Expression) As Expression
        If left.Type.Equals(GetType(String)) Then
            Return Expression.LessThanOrEqual( _
                                              GenerateStaticMethodCall("Compare", left, right), _
                                              Expression.Constant(0))
        End If
        Return Expression.LessThanOrEqual(left, right)
    End Function

    Function GenerateAdd(ByVal left As Expression, ByVal right As Expression) As Expression
        If left.Type.Equals(GetType(String)) AndAlso right.Type.Equals(GetType(String)) Then
            Return GenerateStaticMethodCall("Concat", left, right)
        End If
        Return Expression.Add(left, right)
    End Function

    Function GenerateSubtract(ByVal left As Expression, ByVal right As expression) As Expression
        Return Expression.Subtract(left, right)
    End Function

    Function GenerateStringConcat(ByVal left As Expression, ByVal right As Expression) As Expression
        Return Expression.Call( _
                               Nothing, _
                               GetType(String).GetMethod("Concat", New Type() {GetType(Object), GetType(Object)}), _
                               New Expression() {left, right})
    End Function

    Function GetStaticMethod(ByVal methodName As String, ByVal left As expression, ByVal right As expression) As MethodInfo
        Return left.Type.GetMethod(methodName, New Type() {left.Type, right.Type})
    End Function

    Function GenerateStaticMethodCall(ByVal methodName As String, ByVal left As Expression, ByVal right As Expression) As Expression
        Return Expression.Call(Nothing, GetStaticMethod(methodName, left, right), New Expression() {left, right})
    End Function

    Sub SetTextPos(ByVal pos As Integer)
        textPos = pos
        ch = If(textPos < textLen, text(textPos), ChrW(0))
    End Sub

    Sub NextChar()
        If textPos < textLen Then textPos += 1
        ch = If(textPos < textLen, text(textPos), ChrW(0))
    End Sub

    Sub NextToken()
        Do While Char.IsWhiteSpace(ch)
            NextChar()
        Loop

        Dim t As TokenId
        Dim tokenPos = textPos
        Select Case ch
            Case "!"c
                NextChar()
                If ch = "=" Then
                    NextChar()
                    t = TokenId.ExclamationEqual
                Else
                    t = TokenId.Exclamation
                End If
            Case "%"c
                NextChar()
                t = TokenId.Percent
            Case "&"c
                NextChar()
                If ch = "&" Then
                    NextChar()
                    t = TokenId.DoubleAmphersand
                Else
                    t = TokenId.Amphersand
                End If
            Case "("c
                NextChar()
                t = TokenId.OpenParen
            Case ")"c
                NextChar()
                t = TokenId.CloseParen
            Case "*"c
                NextChar()
                t = TokenId.Asterisk
            Case "+"c
                NextChar()
                t = TokenId.Plus
            Case ","c
                NextChar()
                t = TokenId.Comma
            Case "-"c
                NextChar()
                t = TokenId.Minus
            Case "."c
                NextChar()
                t = TokenId.Dot
            Case "/"c
                NextChar()
                t = TokenId.Slash
            Case ":"c
                NextChar()
                t = TokenId.Colon
            Case "<"c
                NextChar()
                If ch = "=" Then
                    NextChar()
                    t = TokenId.LessThanEqual
                ElseIf ch = ">" Then
                    NextChar()
                    t = TokenId.LessGreater
                Else
                    t = TokenId.LessThan
                End If
            Case "="c
                NextChar()
                If ch = "=" Then
                    NextChar()
                    t = TokenId.DoubleEqual
                Else
                    t = TokenId.Equal
                End If
            Case ">"c
                NextChar()
                If ch = "=" Then
                    NextChar()
                    t = TokenId.GreaterThanEqual
                Else
                    t = TokenId.GreaterThan
                End If
            Case "?"c
                NextChar()
                t = TokenId.Question
            Case "["c
                NextChar()
                t = TokenId.OpenBracket
            Case "]"c
                NextChar()
                t = TokenId.CloseBracket
            Case "|"c
                NextChar()
                If ch = "|" Then
                    NextChar()
                    t = TokenId.DoubleBar
                Else
                    t = TokenId.Bar
                End If
            Case "'"c, """"c
                Dim quote = ch
                Do
                    NextChar()
                    Do While textPos < textLen AndAlso ch <> quote
                        NextChar()
                    Loop
                    If textPos = textLen Then Throw ParseError(textPos, Res.UnterminatedStringLiteral)
                    NextChar()
                Loop While ch = quote
                t = TokenId.StringLiteral
            Case Else
                If Char.IsLetter(ch) OrElse ch = "@" OrElse ch = "_" Then
                    Do
                        NextChar()
                    Loop While Char.IsLetterOrDigit(ch) OrElse ch = "_"
                    t = TokenId.Identifier
                    Exit Select
                End If

                If Char.IsDigit(ch) Then
                    t = TokenId.IntegerLiteral
                    Do
                        NextChar()
                    Loop While Char.IsDigit(ch)
                    If ch = "." Then
                        t = TokenId.RealLiteral
                        NextChar()
                        ValidateDigit()
                        Do
                            NextChar()
                        Loop While Char.IsDigit(ch)
                    End If
                    If ch = "E" OrElse ch = "e" Then
                        t = TokenId.RealLiteral
                        NextChar()
                        If ch = "+" OrElse ch = "-" Then NextChar()
                        ValidateDigit()
                        Do
                            NextChar()
                        Loop While Char.IsDigit(ch)
                    End If
                    If ch = "F" Or ch = "f" Then NextChar()
                    Exit Select
                End If
                If textPos = textLen Then
                    t = TokenId.End
                    Exit Select
                End If
                Throw ParseError(textPos, Res.InvalidCharacter, ch)
        End Select
        tokenVal.id = t
        tokenVal.text = text.Substring(tokenPos, textPos - tokenPos)
        tokenVal.pos = tokenPos
    End Sub

    Function TokenIdentifierIs(ByVal id As String) As Boolean
        Return tokenVal.id = TokenId.Identifier AndAlso String.Equals(id, tokenVal.text, StringComparison.OrdinalIgnoreCase)
    End Function

    Function GetIdentifier() As String
        ValidateToken(TokenId.Identifier, Res.IdentifierExpected)
        Dim id = tokenVal.text
        If id.Length > 1 AndAlso id(0) = "@" Then id = id.Substring(1)
        Return id
    End Function

    Sub ValidateDigit()
        If Not Char.IsDigit(ch) Then Throw ParseError(textPos, Res.DigitExpected)
    End Sub

    Sub ValidateToken(ByVal t As TokenId, ByVal errorMessage As String)
        If tokenVal.id <> t Then Throw ParseError(errorMessage)
    End Sub

    Sub ValidateToken(ByVal t As TokenId)
        If tokenVal.id <> t Then Throw ParseError(Res.SyntaxError)
    End Sub

    Overloads Function ParseError(ByVal format As String, ByVal ParamArray args As Object()) As Exception
        Return ParseError(tokenVal.pos, format, args)
    End Function

    Overloads Function ParseError(ByVal pos As Integer, ByVal format As String, ByVal ParamArray args As Object()) As Exception
        Return New ParseException(String.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos)
    End Function

    Shared Function CreateKeywords() As Dictionary(Of String, Object)
        Dim d As New Dictionary(Of String, Object)(StringComparer.OrdinalIgnoreCase)

        d.Add("true", trueLiteral)
        d.Add("false", falseLiteral)
        d.Add("null", nullLiteral)
        d.Add(keywordIt, keywordIt)
        d.Add(keywordIif, keywordIif)
        d.Add(keywordNew, keywordNew)

        For Each type As Type In predefinedTypes
            d.Add(type.Name, type)
        Next

        Return d
    End Function
End Class

Class Res
    Public Const DuplicateIdentifier As String = "The identifier '{0}' was defined more than once"
    Public Const ExpressionTypeMismatch As String = "Expression of type '{0}' expected"
    Public Const ExpressionExpected As String = "Expression expected"
    Public Const InvalidCharacterLiteral As String = "Character literal must contain exactly one character"
    Public Const InvalidIntegerLiteral As String = "Invalid integer literal '{0}'"
    Public Const InvalidRealLiteral As String = "Invalid real literal '{0}'"
    Public Const UnknownIdentifier As String = "Unknown identifier '{0}'"
    Public Const NoItInScope As String = "No 'it' is in scope"
    Public Const IifRequiresThreeArgs As String = "The 'iif' function requires three arguments"
    Public Const FirstExprMustBeBool As String = "The first expression must be of type 'Boolean'"
    Public Const BothTypesConvertToOther As String = "Both of the types '{0}' and '{1}' convert to the other"
    Public Const NeitherTypeConvertsToOther As String = "Neither of the types '{0}' and '{1}' converts to the other"
    Public Const MissingAsClause As String = "Expression is missing an 'as' clause"
    Public Const ArgsIncompatibleWithLambda As String = "Argument list incompatible with lambda expression"
    Public Const TypeHasNoNullableForm As String = "Type '{0}' has no nullable form"
    Public Const NoMatchingConstructor As String = "No matching constructor in type '{0}'"
    Public Const AmbiguousConstructorInvocation As String = "Ambiguous invocation of '{0}' constructor"
    Public Const CannotConvertValue As String = "A value of type '{0}' cannot be converted to type '{1}'"
    Public Const NoApplicableMethod As String = "No applicable method '{0}' exists in type '{1}'"
    Public Const MethodsAreInaccessible As String = "Methods on type '{0}' are not accessible"
    Public Const MethodIsVoid As String = "Method '{0}' in type '{1}' does not return a value"
    Public Const AmbiguousMethodInvocation As String = "Ambiguous invocation of method '{0}' in type '{1}'"
    Public Const UnknownPropertyOrField As String = "No property or field '{0}' exists in type '{1}'"
    Public Const NoApplicableAggregate As String = "No applicable aggregate method '{0}' exists"
    Public Const CannotIndexMultiDimArray As String = "Indexing of multi-dimensional arrays is not supported"
    Public Const InvalidIndex As String = "Array index must be an integer expression"
    Public Const NoApplicableIndexer As String = "No applicable indexer exists in type '{0}'"
    Public Const AmbiguousIndexerInvocation As String = "Ambiguous invocation of indexer in type '{0}'"
    Public Const IncompatibleOperand As String = "Operator '{0}' incompatible with operand type '{1}'"
    Public Const IncompatibleOperands As String = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"
    Public Const UnterminatedStringLiteral As String = "Unterminated string literal"
    Public Const InvalidCharacter As String = "Syntax error '{0}'"
    Public Const DigitExpected As String = "Digit expected"
    Public Const SyntaxError As String = "Syntax error"
    Public Const TokenExpected As String = "{0} expected"
    Public Const ParseExceptionFormat As String = "{0} (at index {1})"
    Public Const ColonExpected As String = "':' expected"
    Public Const OpenParenExpected As String = "'(' expected"
    Public Const CloseParenOrOperatorExpected As String = "')' or operator expected"
    Public Const CloseParenOrCommaExpected As String = "')' or ',' expected"
    Public Const DotOrOpenParenExpected As String = "'.' or '(' expected"
    Public Const OpenBracketExpected As String = "'[' expected"
    Public Const CloseBracketOrCommaExpected As String = "']' or ',' expected"
    Public Const IdentifierExpected As String = "Identifier expected"
End Class