'Coded by: Electroalek<http://electroalek.com/>, 2014
Imports System
Imports System.Text
Imports System.Xml.Serialization
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Class JSONSerializer

#Region "Deserializer"

    Public Shared Function Deserialize(ByVal input As String) As JSON
        Dim output As New JSON
        Dim Tree As ParseTree = New Parser(New Scanner).Parse(input)
        If Tree.Errors.Count = 0 Then
            For Each l As KeyValuePair(Of String, Object) In GetPairs(Tree.Nodes(0).Nodes(0).Nodes)
                output.Add(l.Key, l.Value)
            Next
        End If
        Return output
    End Function

    Private Shared Function GetPairs(ByVal nodes As List(Of ParseNode)) As JSON
        Dim output As New JSON
        For Each n As ParseNode In nodes
            Select Case n.Token.Type
                Case TokenType.PAIR
                    output.Add(GetPair(n.Nodes))
                Case TokenType.BLOCK
                    output.Add(GetPairs(n.Nodes))
            End Select
        Next
        Return output
    End Function

    Private Shared Function GetPair(ByVal nodes As List(Of ParseNode)) As KeyValuePair(Of String, Object)
        Dim key As String = String.Empty
        Dim value As Object = Nothing
        For Each n As ParseNode In nodes
            Select Case n.Token.Type
                Case TokenType.P_KEY
                    key = RemoveQuotes(n.Nodes(0).Token.Text)
                Case TokenType.P_VALUE
                    value = GetValue(n.Nodes)
            End Select
        Next
        Return New KeyValuePair(Of String, Object)(key, value)
    End Function

    Private Shared Function GetValue(ByVal nodes As List(Of ParseNode)) As Object
        Dim output As Object = Nothing
        For Each n As ParseNode In nodes
            Select Case n.Token.Type
                Case TokenType.VALUE
                    Return GetValueType(n.Nodes)
                Case TokenType.BLOCK
                    Return GetPairs(n.Nodes)
            End Select
        Next
        Return output
    End Function

    Private Shared Function GetValueType(ByVal nodes As List(Of ParseNode)) As Object
        For Each n As ParseNode In nodes
            Select Case n.Token.Type
                Case TokenType.T_STR
                    Return RemoveQuotes(n.Token.Text)
                Case TokenType.T_INT
                    Return Integer.Parse(n.Token.Text)
                Case TokenType.T_DBL
                    Return Double.Parse(n.Token.Text)
                Case TokenType.T_BOOL
                    Return Boolean.Parse(n.Token.Text)
            End Select
        Next
        Return Nothing
    End Function

#Region "TinyPG"

    '<% @TinyPG Language="vb" %>

    'T_INT       -> @"(?!,)([+-]*[0-9]+)(?<!,)";
    'T_DBL       -> @"(?!,)([+-]*[0-9]+[.,][0-9]+)(?<!,)";
    'T_STR       -> @"""(.*?)""";
    'T_BOOL      -> @"True|False";
    'C_OPEN      -> @"\{|\[";
    'C_CLOSE     -> @"\}|\]";
    'C_COMMA     -> @"\,";
    'C_COLON     -> @"\:";
    'EOF         -> @"^$";

    '[Skip] WS   -> @"(\s|\t)+";
    'Start       -> BLOCK EOF;
    'P_KEY       -> T_STR;
    'P_VALUE     -> VALUE | BLOCK;
    'PAIR        -> P_KEY C_COLON P_VALUE;
    'BLOCK       -> C_OPEN (PAIR | BLOCK | VALUE | C_COLON | C_COMMA)* C_CLOSE;
    'VALUE       -> T_STR | T_INT | T_DBL | T_BOOL;

    Private Enum TokenType

        'Non terminal tokens:
        _NONE_ = 0
        _UNDETERMINED_ = 1

        'Non terminal tokens:
        Start = 2
        P_KEY = 3
        P_VALUE = 4
        PAIR = 5
        BLOCK = 6
        VALUE = 7

        'Terminal tokens:
        T_INT = 8
        T_DBL = 9
        T_STR = 10
        T_BOOL = 11
        C_OPEN = 12
        C_CLOSE = 13
        C_COMMA = 14
        C_COLON = 15
        EOF = 16
        WS = 17
    End Enum

    Private Class Parser

        Private m_scanner As Scanner
        Private m_tree As ParseTree

        Public Sub New(ByVal scanner As Scanner)
            m_scanner = scanner
        End Sub

        Public Function Parse(ByVal input As String) As ParseTree
            m_tree = New ParseTree()
            Return Parse(input, m_tree)
        End Function

        Public Function Parse(ByVal input As String, ByVal tree As ParseTree) As ParseTree
            m_scanner.Init(input)

            m_tree = tree
            ParseStart(m_tree)
            m_tree.Skipped = m_scanner.Skipped

            Return m_tree
        End Function

        Private Sub ParseStart(ByVal parent As ParseNode)
            Dim tok As Token
            Dim n As ParseNode
            Dim node As ParseNode = parent.CreateNode(m_scanner.GetToken(TokenType.Start), "Start")
            parent.Nodes.Add(node)



            ParseBLOCK(node)


            tok = m_scanner.Scan(TokenType.EOF)
            n = node.CreateNode(tok, tok.ToString())
            node.Token.UpdateRange(tok)
            node.Nodes.Add(n)
            If tok.Type <> TokenType.EOF Then
                m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.EOF.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                Return

            End If


            parent.Token.UpdateRange(node.Token)
        End Sub

        Private Sub ParseP_KEY(ByVal parent As ParseNode)
            Dim tok As Token
            Dim n As ParseNode
            Dim node As ParseNode = parent.CreateNode(m_scanner.GetToken(TokenType.P_KEY), "P_KEY")
            parent.Nodes.Add(node)

            tok = m_scanner.Scan(TokenType.T_STR)
            n = node.CreateNode(tok, tok.ToString())
            node.Token.UpdateRange(tok)
            node.Nodes.Add(n)
            If tok.Type <> TokenType.T_STR Then
                m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.T_STR.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                Return

            End If


            parent.Token.UpdateRange(node.Token)
        End Sub

        Private Sub ParseP_VALUE(ByVal parent As ParseNode)
            Dim tok As Token
            Dim node As ParseNode = parent.CreateNode(m_scanner.GetToken(TokenType.P_VALUE), "P_VALUE")
            parent.Nodes.Add(node)

            tok = m_scanner.LookAhead(TokenType.T_STR, TokenType.T_INT, TokenType.T_DBL, TokenType.T_BOOL, TokenType.C_OPEN)
            Select Case tok.Type

                Case TokenType.T_STR
                    ParseVALUE(node)
                Case TokenType.T_INT
                    ParseVALUE(node)
                Case TokenType.T_DBL
                    ParseVALUE(node)
                Case TokenType.T_BOOL
                    ParseVALUE(node)
                Case TokenType.C_OPEN
                    ParseBLOCK(node)
                Case Else
                    m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found.", &H2, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                    Exit Select
            End Select

            parent.Token.UpdateRange(node.Token)
        End Sub

        Private Sub ParsePAIR(ByVal parent As ParseNode)
            Dim tok As Token
            Dim n As ParseNode
            Dim node As ParseNode = parent.CreateNode(m_scanner.GetToken(TokenType.PAIR), "PAIR")
            parent.Nodes.Add(node)



            ParseP_KEY(node)


            tok = m_scanner.Scan(TokenType.C_COLON)
            n = node.CreateNode(tok, tok.ToString())
            node.Token.UpdateRange(tok)
            node.Nodes.Add(n)
            If tok.Type <> TokenType.C_COLON Then
                m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.C_COLON.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                Return

            End If



            ParseP_VALUE(node)

            parent.Token.UpdateRange(node.Token)
        End Sub

        Private Sub ParseBLOCK(ByVal parent As ParseNode)
            Dim tok As Token
            Dim n As ParseNode
            Dim node As ParseNode = parent.CreateNode(m_scanner.GetToken(TokenType.BLOCK), "BLOCK")
            parent.Nodes.Add(node)



            tok = m_scanner.Scan(TokenType.C_OPEN)
            n = node.CreateNode(tok, tok.ToString())
            node.Token.UpdateRange(tok)
            node.Nodes.Add(n)
            If tok.Type <> TokenType.C_OPEN Then
                m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.C_OPEN.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                Return

            End If



            tok = m_scanner.LookAhead(TokenType.T_STR, TokenType.C_OPEN, TokenType.T_INT, TokenType.T_DBL, TokenType.T_BOOL, TokenType.C_COLON, TokenType.C_COMMA)
            While tok.Type = TokenType.T_STR Or tok.Type = TokenType.C_OPEN Or tok.Type = TokenType.T_INT Or tok.Type = TokenType.T_DBL Or tok.Type = TokenType.T_BOOL Or tok.Type = TokenType.C_COLON Or tok.Type = TokenType.C_COMMA
                tok = m_scanner.LookAhead(TokenType.T_STR, TokenType.C_OPEN, TokenType.T_INT, TokenType.T_DBL, TokenType.T_BOOL, TokenType.C_COLON, TokenType.C_COMMA)
                Select Case tok.Type

                    Case TokenType.T_STR
                        ParsePAIR(node)
                    Case TokenType.C_OPEN
                        ParseBLOCK(node)
                    Case TokenType.T_STR
                        ParseVALUE(node)
                    Case TokenType.T_INT
                        ParseVALUE(node)
                    Case TokenType.T_DBL
                        ParseVALUE(node)
                    Case TokenType.T_BOOL
                        ParseVALUE(node)
                    Case TokenType.C_COLON
                        tok = m_scanner.Scan(TokenType.C_COLON)
                        n = node.CreateNode(tok, tok.ToString())
                        node.Token.UpdateRange(tok)
                        node.Nodes.Add(n)
                        If tok.Type <> TokenType.C_COLON Then
                            m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.C_COLON.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                            Return

                        End If

                    Case TokenType.C_COMMA
                        tok = m_scanner.Scan(TokenType.C_COMMA)
                        n = node.CreateNode(tok, tok.ToString())
                        node.Token.UpdateRange(tok)
                        node.Nodes.Add(n)
                        If tok.Type <> TokenType.C_COMMA Then
                            m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.C_COMMA.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                            Return

                        End If

                    Case Else
                        m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found.", &H2, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                        Exit Select
                End Select
                tok = m_scanner.LookAhead(TokenType.T_STR, TokenType.C_OPEN, TokenType.T_INT, TokenType.T_DBL, TokenType.T_BOOL, TokenType.C_COLON, TokenType.C_COMMA)
            End While


            tok = m_scanner.Scan(TokenType.C_CLOSE)
            n = node.CreateNode(tok, tok.ToString())
            node.Token.UpdateRange(tok)
            node.Nodes.Add(n)
            If tok.Type <> TokenType.C_CLOSE Then
                m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.C_CLOSE.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                Return

            End If


            parent.Token.UpdateRange(node.Token)
        End Sub

        Private Sub ParseVALUE(ByVal parent As ParseNode)
            Dim tok As Token
            Dim n As ParseNode
            Dim node As ParseNode = parent.CreateNode(m_scanner.GetToken(TokenType.VALUE), "VALUE")
            parent.Nodes.Add(node)

            tok = m_scanner.LookAhead(TokenType.T_STR, TokenType.T_INT, TokenType.T_DBL, TokenType.T_BOOL)
            Select Case tok.Type

                Case TokenType.T_STR
                    tok = m_scanner.Scan(TokenType.T_STR)
                    n = node.CreateNode(tok, tok.ToString())
                    node.Token.UpdateRange(tok)
                    node.Nodes.Add(n)
                    If tok.Type <> TokenType.T_STR Then
                        m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.T_STR.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                        Return

                    End If

                Case TokenType.T_INT
                    tok = m_scanner.Scan(TokenType.T_INT)
                    n = node.CreateNode(tok, tok.ToString())
                    node.Token.UpdateRange(tok)
                    node.Nodes.Add(n)
                    If tok.Type <> TokenType.T_INT Then
                        m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.T_INT.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                        Return

                    End If

                Case TokenType.T_DBL
                    tok = m_scanner.Scan(TokenType.T_DBL)
                    n = node.CreateNode(tok, tok.ToString())
                    node.Token.UpdateRange(tok)
                    node.Nodes.Add(n)
                    If tok.Type <> TokenType.T_DBL Then
                        m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.T_DBL.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                        Return

                    End If

                Case TokenType.T_BOOL
                    tok = m_scanner.Scan(TokenType.T_BOOL)
                    n = node.CreateNode(tok, tok.ToString())
                    node.Token.UpdateRange(tok)
                    node.Nodes.Add(n)
                    If tok.Type <> TokenType.T_BOOL Then
                        m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found. Expected " + TokenType.T_BOOL.ToString(), &H1001, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                        Return

                    End If

                Case Else
                    m_tree.Errors.Add(New ParseError("Unexpected token '" + tok.Text.Replace("\n", "") + "' found.", &H2, 0, tok.StartPos, tok.StartPos, tok.EndPos - tok.StartPos))
                    Exit Select
            End Select

            parent.Token.UpdateRange(node.Token)
        End Sub

    End Class

    Private Class Scanner

        Public Input As String
        Public StartPos As Integer = 0
        Public EndPos As Integer = 0
        Public CurrentLine As Integer
        Public CurrentColumn As Integer
        Public CurrentPosition As Integer
        Public Skipped As List(Of Token)
        Public Patterns As Dictionary(Of TokenType, Regex)

        Private LookAheadToken As Token
        Private Tokens As List(Of TokenType)
        Private SkipList As List(Of TokenType)

        Public Sub New()
            Dim regex As Regex
            Patterns = New Dictionary(Of TokenType, Regex)()
            Tokens = New List(Of TokenType)()
            LookAheadToken = Nothing
            Skipped = New List(Of Token)()

            SkipList = New List(Of TokenType)()
            SkipList.Add(TokenType.WS)

            regex = New Regex("(?!,)([+-]*[0-9]+)(?<!,)", RegexOptions.Compiled)
            Patterns.Add(TokenType.T_INT, regex)
            Tokens.Add(TokenType.T_INT)

            regex = New Regex("(?!,)([+-]*[0-9]+[.,][0-9]+)(?<!,)", RegexOptions.Compiled)
            Patterns.Add(TokenType.T_DBL, regex)
            Tokens.Add(TokenType.T_DBL)

            regex = New Regex("""(.*?)""", RegexOptions.Compiled)
            Patterns.Add(TokenType.T_STR, regex)
            Tokens.Add(TokenType.T_STR)

            regex = New Regex("True|False", RegexOptions.Compiled)
            Patterns.Add(TokenType.T_BOOL, regex)
            Tokens.Add(TokenType.T_BOOL)

            regex = New Regex("\{|\[", RegexOptions.Compiled)
            Patterns.Add(TokenType.C_OPEN, regex)
            Tokens.Add(TokenType.C_OPEN)

            regex = New Regex("\}|\]", RegexOptions.Compiled)
            Patterns.Add(TokenType.C_CLOSE, regex)
            Tokens.Add(TokenType.C_CLOSE)

            regex = New Regex("\,", RegexOptions.Compiled)
            Patterns.Add(TokenType.C_COMMA, regex)
            Tokens.Add(TokenType.C_COMMA)

            regex = New Regex("\:", RegexOptions.Compiled)
            Patterns.Add(TokenType.C_COLON, regex)
            Tokens.Add(TokenType.C_COLON)

            regex = New Regex("^$", RegexOptions.Compiled)
            Patterns.Add(TokenType.EOF, regex)
            Tokens.Add(TokenType.EOF)

            regex = New Regex("(\s|\t)+", RegexOptions.Compiled)
            Patterns.Add(TokenType.WS, regex)
            Tokens.Add(TokenType.WS)



        End Sub

        Public Sub Init(ByVal input As String)
            Me.Input = input
            StartPos = 0
            EndPos = 0
            CurrentLine = 0
            CurrentColumn = 0
            CurrentPosition = 0
            Skipped = New List(Of Token)()
            LookAheadToken = Nothing
        End Sub

        Public Function GetToken(ByVal type As TokenType) As Token
            Dim t As New Token(Me.StartPos, Me.EndPos)
            t.Type = type
            Return t
        End Function

        Public Function Scan(ByVal ParamArray expectedtokens As TokenType()) As Token
            Dim tok As Token = LookAhead(expectedtokens)
            ' temporarely retrieve the lookahead
            LookAheadToken = Nothing
            ' reset lookahead token, so scanning will continue
            StartPos = tok.EndPos
            EndPos = tok.EndPos
            ' set the tokenizer to the new scan position
            Return tok
        End Function

        Public Function LookAhead(ByVal ParamArray expectedtokens As TokenType()) As Token
            Dim i As Integer
            Dim start As Integer = StartPos
            Dim tok As Token = Nothing
            Dim scantokens As List(Of TokenType)

            ' this prevents double scanning and matching
            ' increased performance
            If LookAheadToken IsNot Nothing AndAlso LookAheadToken.Type <> TokenType._UNDETERMINED_ AndAlso LookAheadToken.Type <> TokenType._NONE_ Then
                Return LookAheadToken
            End If

            If expectedtokens.Length = 0 Then
                scantokens = Tokens
            Else
                scantokens = New List(Of TokenType)(expectedtokens)
                scantokens.AddRange(SkipList)
            End If

            Do
                Dim len As Integer = -1
                Dim index As TokenType = Integer.MaxValue
                Dim m_input As String = Input.Substring(start)

                tok = New Token(start, EndPos)


                For i = 0 To scantokens.Count - 1
                    Dim r As Regex = Patterns(scantokens(i))
                    Dim m As Match = r.Match(m_input)
                    If m.Success AndAlso m.Index = 0 AndAlso ((m.Length > len) OrElse (scantokens(i) < index AndAlso m.Length = len)) Then
                        len = m.Length
                        index = scantokens(i)
                    End If
                Next i

                If index >= 0 AndAlso len >= 0 Then
                    tok.EndPos = start + len
                    tok.Text = Input.Substring(tok.StartPos, len)
                    tok.Type = index
                Else
                    If tok.StartPos < tok.EndPos - 1 Then
                        tok.Text = Input.Substring(tok.StartPos, 1)
                    End If
                End If

                If SkipList.Contains(tok.Type) Then
                    start = tok.EndPos
                    Skipped.Add(tok)
                Else
                    tok.Skipped = Skipped
                    Skipped = New List(Of Token)
                End If
            Loop While SkipList.Contains(tok.Type)

            LookAheadToken = tok
            Return tok
        End Function

    End Class

    Private Class Token

        Private m_startPos As Integer
        Private m_endPos As Integer
        Private m_text As String
        Private m_value As Object
        Private m_skipped As List(Of Token)

        Public Property StartPos() As Integer
            Get
                Return m_startPos
            End Get
            Set(ByVal value As Integer)
                m_startPos = value
            End Set
        End Property

        Public Property EndPos() As Integer
            Get
                Return m_endPos
            End Get
            Set(ByVal value As Integer)
                m_endPos = value
            End Set
        End Property

        Public ReadOnly Property Length() As Integer
            Get
                Return m_endPos - m_startPos
            End Get
        End Property

        Public Property Text() As String
            Get
                Return m_text
            End Get
            Set(ByVal value As String)
                m_text = value
            End Set
        End Property

        Public Property Skipped() As List(Of Token)
            Get
                Return m_skipped
            End Get
            Set(ByVal value As List(Of Token))
                m_skipped = value
            End Set
        End Property

        Public Property Value() As Object
            Get
                Return m_value
            End Get
            Set(ByVal value As Object)
                Me.m_value = value
            End Set
        End Property

        <XmlAttribute()> _
        Public Type As TokenType

        Public Sub New()
            Me.New(0, 0)
        End Sub

        Public Sub New(ByVal start As Integer, ByVal endPos As Integer)
            Type = TokenType._UNDETERMINED_
            m_startPos = start
            m_endPos = endPos
            Text = ""
            ' must initialize with empty string, may cause null reference exceptions otherwise
            Value = Nothing
        End Sub

        Public Sub UpdateRange(ByVal token As Token)
            If token.StartPos < m_startPos Then
                m_startPos = token.StartPos
            End If
            If token.EndPos > m_endPos Then
                m_endPos = token.EndPos
            End If
        End Sub

        Public Overloads Overrides Function ToString() As String
            If Text <> Nothing Then
                Return Type.ToString() + " '" + Text + "'"
            Else
                Return Type.ToString()
            End If
        End Function

    End Class

    <Serializable()> _
    Private Class ParseErrors
        Inherits List(Of ParseError)

        Public Sub New()

        End Sub

    End Class

    <Serializable()> _
    Private Class ParseError

        Private m_message As String
        Private m_code As Integer
        Private m_line As Integer
        Private m_col As Integer
        Private m_pos As Integer
        Private m_length As Integer

        Public ReadOnly Property Code() As Integer
            Get
                Return m_code
            End Get
        End Property

        Public ReadOnly Property Line() As Integer
            Get
                Return m_line
            End Get
        End Property

        Public ReadOnly Property Column() As Integer
            Get
                Return m_col
            End Get
        End Property

        Public ReadOnly Property Position() As Integer
            Get
                Return m_pos
            End Get
        End Property

        Public ReadOnly Property Length() As Integer
            Get
                Return m_length
            End Get
        End Property

        Public ReadOnly Property Message() As String
            Get
                Return m_message
            End Get
        End Property

        Public Sub New(ByVal message As String, ByVal code As Integer, ByVal node As ParseNode)
            Me.New(message, code, 0, node.Token.StartPos, node.Token.StartPos, node.Token.Length)
        End Sub

        Public Sub New(ByVal message As String, ByVal code As Integer, ByVal line As Integer, ByVal col As Integer, ByVal pos As Integer, ByVal length As Integer)
            m_message = message
            m_code = code
            m_line = line
            m_col = col
            m_pos = pos
            m_length = length
        End Sub

    End Class

    <Serializable()> _
    Private Class ParseTree
        Inherits ParseNode

        Public Errors As ParseErrors
        Public Skipped As List(Of Token)

        Public Sub New()
            MyBase.New(New Token(), "ParseTree")
            Token.Type = TokenType.Start
            Token.Text = "Root"
            Skipped = New List(Of Token)()
            Errors = New ParseErrors()
        End Sub

        Public Function PrintTree() As String
            Dim sb As New StringBuilder()
            Dim indent As Integer = 0
            PrintNode(sb, Me, indent)
            Return sb.ToString()
        End Function

        Private Sub PrintNode(ByVal sb As StringBuilder, ByVal node As ParseNode, ByVal indent As Integer)

            Dim space As String = "".PadLeft(indent, " "c)

            sb.Append(space)
            sb.AppendLine(node.Text)

            For Each n As ParseNode In node.Nodes
                PrintNode(sb, n, indent + 2)
            Next
        End Sub

        Public Overloads Function Eval(ByVal ParamArray paramlist As Object()) As Object
            Return Nodes(0).Eval(Me, paramlist)
        End Function

    End Class

    <Serializable()>
    <XmlInclude(GetType(ParseTree))>
    Private Class ParseNode

        Protected m_text As String
        Protected m_nodes As List(Of ParseNode)

        Public ReadOnly Property Nodes() As List(Of ParseNode)
            Get
                Return m_nodes
            End Get
        End Property

        <XmlIgnore()> _
        Public Parent As ParseNode
        Public Token As Token

        <XmlIgnore()> _
        Public Property Text() As String
            ' text to display in parse tree 
            Get
                Return m_text
            End Get
            Set(ByVal value As String)
                m_text = value
            End Set
        End Property

        Public Overridable Function CreateNode(ByVal token As Token, ByVal text As String) As ParseNode
            Dim node As New ParseNode(token, text)
            node.Parent = Me
            Return node
        End Function

        Protected Sub New(ByVal token As Token, ByVal text As String)
            Me.Token = token
            m_text = text
            m_nodes = New List(Of ParseNode)()
        End Sub

        Protected Function GetValue(ByVal tree As ParseTree, ByVal type As TokenType, ByVal index As Integer) As Object
            Return GetValueByRef(tree, type, index)
        End Function

        Protected Function GetValueByRef(ByVal tree As ParseTree, ByVal type As TokenType, ByRef index As Integer) As Object
            Dim o As Object = Nothing
            If index < 0 Then
                Return o
            End If

            ' left to right
            For Each node As ParseNode In nodes
                If node.Token.Type = type Then
                    System.Math.Max(System.Threading.Interlocked.Decrement(index), index + 1)
                    If index < 0 Then
                        o = node.Eval(tree)
                        Exit For
                    End If
                End If
            Next
            Return o
        End Function

        Friend Function Eval(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Dim Value As Object = Nothing

            Select Case Token.Type
                Case TokenType.Start
                    Value = EvalStart(tree, paramlist)
                    Exit Select
                Case TokenType.P_KEY
                    Value = EvalP_KEY(tree, paramlist)
                    Exit Select
                Case TokenType.P_VALUE
                    Value = EvalP_VALUE(tree, paramlist)
                    Exit Select
                Case TokenType.PAIR
                    Value = EvalPAIR(tree, paramlist)
                    Exit Select
                Case TokenType.BLOCK
                    Value = EvalBLOCK(tree, paramlist)
                    Exit Select
                Case TokenType.VALUE
                    Value = EvalVALUE(tree, paramlist)
                    Exit Select

                Case Else
                    Value = Token.Text
                    Exit Select
            End Select
            Return Value
        End Function

        Protected Overridable Function EvalStart(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Return "Could not interpret input; no semantics implemented."
        End Function

        Protected Overridable Function EvalP_KEY(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Throw New NotImplementedException()
        End Function

        Protected Overridable Function EvalP_VALUE(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Throw New NotImplementedException()
        End Function

        Protected Overridable Function EvalPAIR(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Throw New NotImplementedException()
        End Function

        Protected Overridable Function EvalBLOCK(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Throw New NotImplementedException()
        End Function

        Protected Overridable Function EvalVALUE(ByVal tree As ParseTree, ByVal ParamArray paramlist As Object()) As Object
            Throw New NotImplementedException()
        End Function

    End Class

#End Region

#End Region

#Region "Serializer"

    Public Shared Function Serialize(ByVal data As JSON) As String
        Dim sb As New StringBuilder
        Dim t As Type = Nothing
        sb.Append("{")
        For Each i As KeyValuePair(Of String, Object) In data
            t = i.Value.GetType
            sb.Append(String.Format("""{0}"":", i.Key))
            If IsNumeric(t) Or t Is GetType(Boolean) Then
                sb.Append(i.Value.ToString)
            ElseIf t Is GetType(JSON) Then
                sb.Append(Serialize(i.Value))
            Else
                sb.Append(String.Format("""{0}""", i.Value.ToString))
            End If
            sb.Append(",")
        Next
        sb.Remove(sb.Length - 1, 1)
        sb.Append("}")
        Return sb.ToString
    End Function

#End Region

#Region "Helpers"

    Public Class JSON
        Inherits Dictionary(Of String, Object)

        Private id As Integer = 0

        Public Overloads Sub Add(ByVal key As String, ByVal value As Object)
            If value Is Nothing Then value = String.Empty
            If MyBase.ContainsKey(key) Then
                MyBase.Item(key) = value
            Else
                MyBase.Add(key, value)
            End If
        End Sub

        Public Overloads Sub Add(ByVal item As KeyValuePair(Of String, Object))
            Add(item.Key, item.Value)
        End Sub

        Public Overloads Sub Add(ByVal value As Object)
            Add(id, value)
            id += 1
        End Sub

        Public Overloads Sub Remove(ByVal key As String)
            MyBase.Remove(key)
            id -= 1
        End Sub

    End Class

    Private Shared Function RemoveQuotes(ByVal t As String) As String
        If t(0) = """" Then t = t.Substring(1, t.Length - 1)
        If t(t.Length - 1) = """" Then t = t.Substring(0, t.Length - 1)
        Return t
    End Function

    Private Shared Function IsNumeric(ByVal t As Type) As Boolean
        Select Case Type.GetTypeCode(t)
            Case TypeCode.Byte,
                 TypeCode.Decimal,
                 TypeCode.Double,
                 TypeCode.Int16,
                 TypeCode.Int32,
                 TypeCode.Int64,
                 TypeCode.SByte,
                 TypeCode.Single,
                 TypeCode.UInt16,
                 TypeCode.UInt32,
                 TypeCode.UInt64
                Return True
        End Select
        Return False
    End Function

#End Region

End Class