Exemplo n.º 1
0
        static CodeStatement _ParseVariableDeclarationStatement(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;
            CodeTypeReference ctr = null;

            if (ST.varType != pc.SymbolId)
            {
                ctr = _ParseType(pc);
            }
            else
            {
                pc.Advance();
            }
            var            id   = _ParseIdentifier(pc);
            CodeExpression init = null;

            if (ST.eq == pc.SymbolId)
            {
                pc.Advance();
                init = _ParseExpression(pc);
            }
            else if (null == ctr)
            {
                pc.Error("Variable declaration using var must have an initializer", l, c, p);
            }
            if (ST.semi != pc.SymbolId)
            {
                pc.Error("Expecting ; in variable declaration statement");
            }
            pc.Advance();
            return(new CodeVariableDeclarationStatement(ctr, id, init).Mark(l, c, p, null == ctr));
        }
Exemplo n.º 2
0
        static CodeStatement _ParseWhileStatement(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;

            if (ST.whileKeyword != pc.SymbolId)
            {
                pc.Error("Expecting while");
            }
            pc.Advance();
            if (ST.lparen != pc.SymbolId)
            {
                pc.Error("Expecting ( in while statement");
            }
            pc.Advance();
            var test = _ParseExpression(pc);

            if (ST.rparen != pc.SymbolId)
            {
                pc.Error("Expecting ) in while statement");
            }
            pc.Advance();
            var result = new CodeIterationStatement(new CodeSnippetStatement().SetLoc(l, c, p), test, new CodeSnippetStatement().SetLoc(l, c, p)).Mark(l, c, p);

            result.Statements.AddRange(_ParseStatementOrBlock(pc));
            return(result);
        }
Exemplo n.º 3
0
        static CodeStatement _ParseIfStatement(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;

            if (ST.ifKeyword != pc.SymbolId)
            {
                pc.Error("Expecting if");
            }
            pc.Advance();
            if (ST.lparen != pc.SymbolId)
            {
                pc.Error("Expecting ( in if statement");
            }
            pc.Advance();
            var test = _ParseExpression(pc);

            if (ST.rparen != pc.SymbolId)
            {
                pc.Error("Expecting ) in if statement");
            }
            pc.Advance();
            var result = new CodeConditionStatement(test).Mark(l, c, p);

            result.TrueStatements.AddRange(_ParseStatementOrBlock(pc));
            if (ST.elseKeyword == pc.SymbolId)
            {
                pc.Advance();
                result.FalseStatements.AddRange(_ParseStatementOrBlock(pc));
            }
            return(result);
        }
Exemplo n.º 4
0
        static CodeStatement _ParseForStatement(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;

            if (ST.forKeyword != pc.SymbolId)
            {
                pc.Error("Expecting for");
            }
            pc.Advance();
            if (ST.lparen != pc.SymbolId)
            {
                pc.Error("Expecting ( in for statement");
            }
            pc.Advance();
            var init = _ParseStatement(pc, false);
            var test = _ParseExpression(pc);

            if (ST.semi != pc.SymbolId)
            {
                pc.Error("Expecting ; in for statement");
            }
            pc.Advance();
            CodeStatement  inc  = null;
            CodeExpression ince = null;

            if (ST.rparen != pc.SymbolId)
            {
                ince = _ParseExpression(pc);
            }
            if (ST.rparen != pc.SymbolId)
            {
                pc.Error("Expecting ) in for statement");
            }
            if (null == ince)
            {
                inc = new CodeSnippetStatement().SetLoc(pc);
            }
            else
            {
                var bo = ince as CodeBinaryOperatorExpression;
                if (null != bo && CodeBinaryOperatorType.Assign == bo.Operator)
                {
                    // probably not an attach or detach statement but we can't rule it out
                    var ur = bo.UserData.Contains("slang:unresolved");
                    inc = new CodeAssignStatement(bo.Left, bo.Right).Mark(ince, ur);
                }
                else
                {
                    inc = new CodeExpressionStatement(ince).Mark(ince);
                }
            }
            pc.Advance();
            var result = new CodeIterationStatement(init, test, inc).Mark(l, c, p);

            result.Statements.AddRange(_ParseStatementOrBlock(pc));
            return(result);
        }
Exemplo n.º 5
0
        static CodeCommentStatement _ParseCommentStatement(_PC pc, bool docComments = false)
        {
            var s = pc.Value;

            switch (pc.SymbolId)
            {
            case ST.lineComment:
                pc.Advance(false);
                if (docComments && s.StartsWith("///", StringComparison.InvariantCulture))
                {
                    if (3 < s.Length && char.IsWhiteSpace(s[3]))
                    {
                        return(new CodeCommentStatement(s.Substring(4).TrimEnd('\r'), true));
                    }
                    return(new CodeCommentStatement(s.Substring(3).TrimEnd('\r'), true));
                }
                if (2 < s.Length && char.IsWhiteSpace(s[2]))
                {
                    return(new CodeCommentStatement(s.Substring(3).TrimEnd('\r')));
                }
                return(new CodeCommentStatement(s.Substring(2).TrimEnd('\r')));

            case ST.blockComment:
                pc.Advance(false);
                return(new CodeCommentStatement(s.Substring(2, s.Length - 4)));
            }
            pc.Error("Expecting line comment or block comment");
            return(null);
        }
Exemplo n.º 6
0
        static string _ParseNamespaceName(_PC pc)
        {
            var l      = pc.Line;
            var c      = pc.Column;
            var p      = pc.Position;
            var result = "";

            while (!pc.IsEnded && ST.lbrace != pc.SymbolId && ST.semi != pc.SymbolId)
            {
                if (0 < result.Length)
                {
                    result += ".";
                }
                result += _ParseIdentifier(pc);
                if (ST.lbrace == pc.SymbolId || ST.semi == pc.SymbolId)
                {
                    break;
                }
                var l2 = pc.Line;
                var c2 = pc.Column;
                var p2 = pc.Position;
                if (ST.dot != pc.SymbolId)
                {
                    pc.Error("Expecting . in namespace name");
                }
                pc.Advance();
                if (ST.lbrace == pc.SymbolId || ST.semi == pc.SymbolId)
                {
                    pc.Error("Expecting identifier in namespace name", l2, c2, p2);
                }
            }
            if ("" == result)
            {
                pc.Error("Expecting identifier in namespace name", l, c, p);
            }
            return(result);
        }
Exemplo n.º 7
0
        static CodeCatchClause _ParseCatchClause(_PC pc)
        {
            if (ST.catchKeyword != pc.SymbolId)
            {
                pc.Error("Expecting catch");
            }
            pc.Advance();
            if (ST.lparen != pc.SymbolId)
            {
                pc.Error("Expecting ( in catch clause");
            }
            pc.Advance();
            var result = new CodeCatchClause();

            result.CatchExceptionType = _ParseType(pc);
            if (ST.rparen != pc.SymbolId)
            {
                result.LocalName = _ParseIdentifier(pc);
            }
            if (ST.rparen != pc.SymbolId)
            {
                pc.Error("Expecting ) in catch clause");
            }
            pc.Advance();
            if (ST.lbrace != pc.SymbolId)
            {
                pc.Error("Expecting { in catch clause");
            }
            pc.Advance();
            result.Statements.AddRange(_ParseStatements(pc, true));
            if (ST.rbrace != pc.SymbolId)
            {
                pc.Error("Expecting } in catch clause");
            }
            pc.Advance();
            return(result);
        }
Exemplo n.º 8
0
        static CodeStatement _ParseTryCatchFinallyStatement(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;

            if (ST.tryKeyword != pc.SymbolId)
            {
                pc.Error("Expecting try");
            }
            pc.Advance();
            var result = new CodeTryCatchFinallyStatement().Mark(l, c, p);

            if (ST.lbrace != pc.SymbolId)
            {
                pc.Error("Expecting { in try statement");
            }
            pc.Advance();
            result.TryStatements.AddRange(_ParseStatements(pc, true));
            if (ST.rbrace != pc.SymbolId)
            {
                pc.Error("Expecting } in try statement");
            }
            pc.Advance();
            while (ST.catchKeyword == pc.SymbolId)
            {
                result.CatchClauses.Add(_ParseCatchClause(pc));
            }
            if (0 == result.CatchClauses.Count && ST.finallyKeyword != pc.SymbolId)
            {
                pc.Error("Expecting catch or finally");
            }
            if (ST.finallyKeyword == pc.SymbolId)
            {
                pc.Advance();
                if (ST.lbrace != pc.SymbolId)
                {
                    pc.Error("Expecting { in finally statement");
                }
                pc.Advance();
                result.FinallyStatements.AddRange(_ParseStatements(pc, true));
                if (ST.rbrace != pc.SymbolId)
                {
                    pc.Error("Expecting } in finally statement");
                }
                pc.Advance();
            }
            return(result);
        }
Exemplo n.º 9
0
        static CodeStatementCollection _ParseStatementOrBlock(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;
            CodeStatementCollection result;

            if (ST.lbrace == pc.SymbolId)
            {
                pc.Advance();
                result = _ParseStatements(pc, true);
                if (ST.rbrace != pc.SymbolId)
                {
                    pc.Error("Unterminated statement block", l, c, p);
                }
                pc.Advance();
                return(result);
            }
            result = new CodeStatementCollection();
            result.Add(_ParseStatement(pc, false));
            return(result);
        }
Exemplo n.º 10
0
        static CodeCompileUnit _ParseCompileUnit(_PC pc)
        {
            var l      = pc.Line;
            var c      = pc.Column;
            var p      = pc.Position;
            var result = new CodeCompileUnit().Mark(l, c, p);
            var ns     = new CodeNamespace().Mark(l, c, p);

            result.Namespaces.Add(ns);
            while (ST.directive == pc.SymbolId || ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
            {
                switch (pc.SymbolId)
                {
                case ST.directive:
                    var d = _ParseDirective(pc) as CodeDirective;
                    if (null != d)
                    {
                        result.StartDirectives.Add(d);
                    }
                    break;

                case ST.blockComment:
                    ns.Comments.Add(_ParseCommentStatement(pc));
                    break;

                case ST.lineComment:
                    ns.Comments.Add(_ParseCommentStatement(pc, true));
                    break;
                }
            }
            while (ST.usingKeyword == pc.SymbolId)
            {
                while (ST.directive == pc.SymbolId || ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
                {
                    pc.Advance(false);
                }
                var l2 = pc.Line;
                var c2 = pc.Column;
                var p2 = pc.Position;
                pc.Advance();
                var nsi = new CodeNamespaceImport(_ParseNamespaceName(pc)).SetLoc(l2, c2, p2);
                if (ST.semi != pc.SymbolId)
                {
                    pc.Error("Expecting ; in using declaration");
                }
                pc.Advance(false);
                ns.Imports.Add(nsi);
            }
            while (ST.lbracket == pc.SymbolId)
            {
                var pc2 = pc.GetLookAhead(true);
                pc2.Advance();
                if (ST.assemblyKeyword != pc2.SymbolId)
                {
                    break;
                }
                result.AssemblyCustomAttributes.AddRange(_ParseAttributeGroup(pc, false).Value);
            }
            while (!pc.IsEnded)
            {
                var            startDirs = new CodeDirectiveCollection();
                var            comments  = new CodeCommentStatementCollection();
                CodeLinePragma lp        = null;
                while (ST.directive == pc.SymbolId || ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
                {
                    switch (pc.SymbolId)
                    {
                    case ST.directive:
                        var d   = _ParseDirective(pc);
                        var llp = d as CodeLinePragma;
                        if (null != llp)
                        {
                            lp = llp;
                        }
                        else if (null != d)
                        {
                            startDirs.Add(d as CodeDirective);
                        }
                        break;

                    case ST.blockComment:
                        comments.Add(_ParseCommentStatement(pc));
                        break;

                    case ST.lineComment:
                        comments.Add(_ParseCommentStatement(pc, true));
                        break;
                    }
                }
                if (ST.namespaceKeyword == pc.SymbolId)
                {
                    var nns = _ParseNamespace(pc);
                    nns.Comments.AddRange(comments);
                    result.Namespaces.Add(nns);
                }
                else
                {
                    var t = _ParseTypeDecl(pc, false, pc.Line, pc.Column, pc.Position, null);
                    t.Comments.AddRange(comments);
                    t.StartDirectives.AddRange(startDirs);
                    t.LinePragma = lp;
                    ns.Types.Add(t);
                }
            }
            return(result);
        }
Exemplo n.º 11
0
        static CodeNamespace _ParseNamespace(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;

            var result = new CodeNamespace().Mark(l, c, p);

            while (ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId || ST.directive == pc.SymbolId)
            {
                if (ST.directive != pc.SymbolId)
                {
                    result.Comments.Add(_ParseCommentStatement(pc, true));
                }
            }
            if (ST.namespaceKeyword != pc.SymbolId)
            {
                pc.Error("Expecting namespace");
            }
            pc.Advance();
            result.Name = _ParseNamespaceName(pc);
            if (ST.lbrace != pc.SymbolId)
            {
                pc.Error("Expecing { in namespace declaration");
            }
            pc.Advance(false);
            if (ST.directive == pc.SymbolId || ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
            {
                var pc2 = pc.GetLookAhead(true);
                if (ST.usingKeyword == pc2.SymbolId)
                {
                    pc.Advance();
                }
            }
            while (ST.usingKeyword == pc.SymbolId)
            {
                while (ST.directive == pc.SymbolId || ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
                {
                    pc.Advance(false);
                }
                var l2 = pc.Line;
                var c2 = pc.Column;
                var p2 = pc.Position;
                pc.Advance();
                var nsi = new CodeNamespaceImport(_ParseNamespaceName(pc)).SetLoc(l2, c2, p2);
                if (ST.semi != pc.SymbolId)
                {
                    pc.Error("Expecting ; in using declaration");
                }
                pc.Advance(false);
                result.Imports.Add(nsi);
            }
            while (ST.rbrace != pc.SymbolId)
            {
                result.Types.Add(_ParseTypeDecl(pc, false, pc.Line, pc.Column, pc.Position, null));
            }
            if (ST.rbrace != pc.SymbolId)
            {
                pc.Error("Unterminated namespace declaration", l, c, p);
            }
            pc.Advance(false);
            return(result);
        }
Exemplo n.º 12
0
        // warning - can return null!:
        static object _ParseDirective(_PC pc)
        {
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;

            if (ST.directive != pc.SymbolId)
            {
                pc.Error("Expecting directive");
            }
            var kvp = _ParseDirectiveKvp(pc);

            pc.Advance(false);
            if (0 == string.Compare("#region", kvp.Key, StringComparison.InvariantCulture))
            {
                return(new CodeRegionDirective(CodeRegionMode.Start, kvp.Value));
            }
            if (0 == string.Compare("#endregion", kvp.Key, StringComparison.InvariantCulture))
            {
                return(new CodeRegionDirective(CodeRegionMode.End, kvp.Value));
            }
            if (0 == string.Compare("#line", kvp.Key, StringComparison.InvariantCulture))
            {
                var s = kvp.Value.Trim();
                if (0 == string.Compare("hidden", s, StringComparison.InvariantCulture) ||
                    0 == string.Compare("default", s, StringComparison.InvariantCulture))
                {
                    return(null);
                }
                var i = s.IndexOfAny(new char[] { ' ', '\t' });
                if (0 > i)
                {
                    pc.Error("Malformed line pragma directive", l, c, p);
                }
                var lineNo = 0;
                if (!int.TryParse(s.Substring(0, i), out lineNo))
                {
                    pc.Error("Malformed line pragma directive - expecting line number", l, c, p);
                }
                var file = s.Substring(i + 1).Trim();
                file = _ParseDirectiveQuotedPart(file, l, c, p);
                return(new CodeLinePragma(file, lineNo));
            }
            else if (0 == string.Compare("#pragma", kvp.Key, StringComparison.InvariantCulture))
            {
                var sa = kvp.Value.Split(' ', '\t');
                var sl = new List <string>(sa.Length);
                for (var i = 0; i < sa.Length; i++)
                {
                    if (!string.IsNullOrWhiteSpace(sa[i]))
                    {
                        sl.Add(sa[i]);
                    }
                }
                if (0 == sl.Count)
                {
                    pc.Error("Malformed pragma directive", l, c, p);
                }
                if (0 != string.Compare("checksum", sl[0], StringComparison.InvariantCulture))
                {
                    pc.Error("Unsupported directive " + kvp.Key, l, c, p);
                }
                if (4 != sl.Count)
                {
                    pc.Error("Malformed checksum pragma directive", l, c, p);
                }
                var  fn    = _ParseDirectiveQuotedPart(sl[1], l, c, p);
                var  guid  = _ParseDirectiveQuotedPart(sl[2], l, c, p);
                var  bytes = _ParseDirectiveQuotedPart(sl[3], l, c, p);
                Guid g;
                if (!Guid.TryParse(guid, out g))
                {
                    pc.Error("Invalid guid in checksum pragma directive");
                }
                if (0 != (bytes.Length % 2))
                {
                    pc.Error("Invalid bytes region in checksum pragma directive");
                }
                var ba = new byte[bytes.Length / 2];
                for (var i = 0; i < ba.Length; i++)
                {
                    var ch1 = bytes[i * 2];
                    if (!_IsHexChar(ch1))
                    {
                        pc.Error("Invalid bytes region in checksum pragma directive");
                    }
                    var ch2 = bytes[i * 2 + 1];
                    if (!_IsHexChar(ch2))
                    {
                        pc.Error("Invalid bytes region in checksum pragma directive");
                    }

                    ba[i] = unchecked ((byte)(_FromHexChar(ch1) * 0x10 + _FromHexChar(ch2)));
                }
                return(new CodeChecksumPragma(fn, g, ba));
            }
            pc.Error("Unsupported directive " + kvp.Key, l, c, p);
            return(null);
        }
Exemplo n.º 13
0
        static CodeStatement _ParseStatement(_PC pc, bool includeComments = false)
        {
            #region Preamble
            CodeLinePragma lp        = null;
            var            startDirs = new CodeDirectiveCollection();
            while (ST.directive == pc.SymbolId)
            {
                var d = _ParseDirective(pc);
                if (null != d)
                {
                    var clp = d as CodeLinePragma;
                    if (null != clp)
                    {
                        lp = clp;
                    }
                    else
                    {
                        startDirs.Add(d as CodeDirective);
                    }
                }
                while (!includeComments && ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
                {
                    pc.Advance(false);
                }
            }
            CodeStatement stmt = null;
            if (includeComments && (ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId))
            {
                stmt = _ParseCommentStatement(pc);
                stmt.StartDirectives.AddRange(startDirs);
                if (null != lp)
                {
                    stmt.LinePragma = lp;
                }
            }
            else
            {
                while (ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
                {
                    pc.Advance(false);
                }
            }
            #endregion Preamble
            var l = pc.Line;
            var c = pc.Column;
            var p = pc.Position;
            // if we got here we've parsed our start directives and this isn't a comment statement
            if (null == stmt)
            {
                _PC pc2 = null;
                switch (pc.SymbolId)
                {
                case ST.semi:
                    // can't do much with empty statements
                    pc.Advance();
                    stmt = new CodeSnippetStatement().SetLoc(l, c, p);
                    break;

                case ST.gotoKeyword:
                    pc.Advance();
                    if (ST.identifier != pc.SymbolId)
                    {
                        pc.Error("Expecting label identifier in goto statement");
                    }
                    stmt = new CodeGotoStatement(pc.Value).SetLoc(l, c, p);
                    if (ST.semi != pc.SymbolId)
                    {
                        pc.Error("Expecting ; in goto statement");
                    }
                    pc.Advance();
                    break;

                case ST.returnKeyword:
                    pc.Advance();
                    if (ST.semi != pc.SymbolId)
                    {
                        stmt = new CodeMethodReturnStatement(_ParseExpression(pc)).Mark(l, c, p);
                    }
                    else
                    {
                        stmt = new CodeMethodReturnStatement().SetLoc(l, c, p);
                    }
                    if (ST.semi != pc.SymbolId)
                    {
                        pc.Error("Expecting ; in return statement");
                    }
                    pc.Advance();
                    break;

                case ST.throwKeyword:
                    pc.Advance();
                    var expr = _ParseExpression(pc);
                    stmt = new CodeThrowExceptionStatement(expr).Mark(l, c, p);
                    if (ST.semi != pc.SymbolId)
                    {
                        pc.Error("Expecting ; in throw statement");
                    }
                    pc.Advance();
                    break;

                case ST.ifKeyword:
                    stmt = _ParseIfStatement(pc);
                    break;

                case ST.whileKeyword:
                    stmt = _ParseWhileStatement(pc);
                    break;

                case ST.forKeyword:
                    stmt = _ParseForStatement(pc);
                    break;

                case ST.tryKeyword:
                    stmt = _ParseTryCatchFinallyStatement(pc);
                    break;

                case ST.varType:
                    stmt = _ParseVariableDeclarationStatement(pc);
                    break;

                default:
                    // possibly a var decl, a label statement, or an expression statement
                    if (ST.identifier == pc.SymbolId)
                    {
                        pc2 = pc.GetLookAhead(true);
                        pc2.Advance();
                        if (ST.colon == pc2.SymbolId)                                 // label
                        {
                            var lbl = pc2.Value;
                            pc.Advance();
                            stmt = new CodeLabeledStatement(lbl, new CodeSnippetStatement().SetLoc(l, c, p)).SetLoc(l, c, p);
                            pc2  = null;
                            break;
                        }
                    }
                    pc2 = null;
                    pc2 = pc.GetLookAhead(true);
                    pc2.ResetAdvanceCount();
                    var advc = 0;
                    try
                    {
                        // possibly a var decl
                        stmt = _ParseVariableDeclarationStatement(pc2);
                        advc = pc2.AdvanceCount;
                        while (advc > 0)
                        {
                            pc.Advance(false);
                            --advc;
                        }
                        break;
                    }
                    catch (SlangSyntaxException sx)
                    {
                        try
                        {
                            pc.ResetAdvanceCount();
                            expr = _ParseExpression(pc);
                            if (ST.semi != pc.SymbolId)
                            {
                                pc.Error("Expecting ; in expression statement");
                            }
                            pc.Advance();
                            var bo = expr as CodeBinaryOperatorExpression;
                            if (null != bo && CodeBinaryOperatorType.Assign == bo.Operator)
                            {
                                var ur = bo.UserData.Contains("slang:unresolved");
                                stmt = new CodeAssignStatement(bo.Left, bo.Right).Mark(l, c, p, ur);
                            }
                            else
                            {
                                stmt = new CodeExpressionStatement(expr).Mark(l, c, p);
                            }
                            break;
                        }
                        catch (SlangSyntaxException sx2)
                        {
                            if (pc.AdvanceCount > advc)
                            {
                                throw sx2;
                            }
                            throw sx;
                        }
                    }
                }
            }
            #region Post
            stmt.StartDirectives.AddRange(startDirs);
            if (null != lp)
            {
                stmt.LinePragma = lp;
            }

            while (!includeComments && ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
            {
                pc.Advance(false);
            }
            while (ST.directive == pc.SymbolId && pc.Value.StartsWith("#end", StringComparison.InvariantCulture))
            {
                stmt.EndDirectives.Add(_ParseDirective(pc) as CodeDirective);
                while (!includeComments && ST.lineComment == pc.SymbolId || ST.blockComment == pc.SymbolId)
                {
                    pc.Advance(false);
                }
            }
            #endregion Post
            return(stmt);
        }