Click here to Skip to main content
15,882,152 members
Articles / Programming Languages / C#

Code Spelling Checker Extension for Visual Studio 2010 (VSX)

Rate me:
Please Sign up or sign in to vote.
4.98/5 (43 votes)
7 Apr 2010CPOL6 min read 111K   2.4K   74  
Building a spelling checker for source code as an extension for Visual Studio 2010
using System;
using System.Collections.Generic;
using System.Linq;


namespace DigitalSamurai.SpellSharp.InputProcessing.Processors
{
    public partial class CSharpProcessor
    {
        public partial class CSharpParsingState : ScopedParsingState<ScopeTypes>
        {
            private static readonly Token tokenTrue = new Token ("true", 0, 0, new DocumentPosition (0, 0), LexicalElements.True);
            private static readonly Token tokenFalse = new Token ("false", 0, 0, new DocumentPosition (0, 0), LexicalElements.False);

            private HashSet<string> globalDefinedPreprocessorSymbols = new HashSet<string> ();
            private Dictionary<string, bool> localDefinedPreprocessorSymbols = new Dictionary<string, bool> ();
            private List<bool> preprocessorConditionStack = new List<bool> ();


            public CSharpParsingState (TokenNode currentNode)
                : base (currentNode)
            {
                StartScope (ScopeTypes.None);

                ProcessSilentTokens (true);
            }


            private void ProcessSilentTokens (bool startOfFile)
            {
                while (HasNode)
                {
                    switch (LexicalElement)
                    {
                        case LexicalElements.PreprocessorDefine:
                            DefineLocalSymbol (startOfFile, true);
                            break;

                        case LexicalElements.PreprocessorUndefine:
                            DefineLocalSymbol (startOfFile, false);
                            break;

                        case LexicalElements.PreprocessorIf:
                            Process_If ();
                            break;

                        case LexicalElements.PreprocessorElseIf:
                            Process_ElseIf ();
                            break;

                        case LexicalElements.PreprocessorElse:
                            Process_Else ();
                            break;

                        case LexicalElements.PreprocessorEndIf:
                            Process_EndIf ();
                            break;

                        case LexicalElements.PreprocessorError:
                        case LexicalElements.PreprocessorWarning:
                            Process_Feedback ();
                            break;

                        case LexicalElements.PreprocessorLine:
                        case LexicalElements.PreprocessorPragma:
                            SkipToNextLine ();
                            break;

                        case LexicalElements.PreprocessorRegion:
                        case LexicalElements.PreprocessorEndRegion:
                            SelectTokenUsingProcessorAndMoveToNext (ProcessorType.XmlFragment);
                            break;

                        case LexicalElements.Comment:
                        case LexicalElements.XmlComment:
                            SelectTokenUsingProcessorAndMoveToNext (ProcessorType.XmlFragment);
                            break;

                        default:
                            return;
                    }
                }
            }

            #region -- Preprocessing --
            private void SkipConditionalBranch ()
            {
                int scope = 0;
                while ((0 <= scope) && base.MoveToNext ())
                {
                    switch (LexicalElement)
                    {
                        case LexicalElements.PreprocessorIf:
                            scope++;
                            break;

                        case LexicalElements.PreprocessorEndIf:
                            scope--;
                            break;

                        case LexicalElements.PreprocessorElseIf:
                        case LexicalElements.PreprocessorElse:
                            break;

                        default:
                            continue;
                    }

                    if (scope == 0)
                    {
                        return;
                    }
                }
            }

            private void DefineLocalSymbol (bool startOfFile, bool isDefined)
            {
                if (startOfFile)
                {
                    var symbol = Token.Text.Trim ();
                    if (!symbol.Any ((c) => char.IsWhiteSpace (c)))
                    {
                        localDefinedPreprocessorSymbols[symbol] = isDefined;
                    }
                }
                base.MoveToNext ();
            }

            private void Process_If ()
            {
                var includeSection = ShouldIncludeSection ();

                // push condition
                preprocessorConditionStack.Add (includeSection);

                if (!includeSection)
                {
                    SkipConditionalBranch ();
                }
            }

            private void Process_ElseIf ()
            {
                if (!CurrentConditionHadPreviousInclusion && ShouldIncludeSection ())
                {
                    CurrentConditionHadPreviousInclusion = true;
                }
                else
                {
                    SkipConditionalBranch ();
                }
            }

            private void Process_Else ()
            {
                if (!CurrentConditionHadPreviousInclusion)
                {
                    base.MoveToNext ();
                }
                else
                {
                    SkipConditionalBranch ();
                }
            }

            private void Process_EndIf ()
            {
                // pop condition
                if (preprocessorConditionStack.Count > 0)
                {
                    preprocessorConditionStack.RemoveAt (preprocessorConditionStack.Count - 1);
                }

                SkipToNextLine ();
            }


            private void Process_Feedback ()
            {
                SkipToNextLine
                (
                    () => SelectTokenUsingProcessor (ProcessorType.XmlFragment)
                );
            }

            private bool ShouldIncludeSection ()
            {
                try
                {
                    var expressionList = new List<Token> ();
                    SkipToNextLine
                    (
                        () => expressionList.Add (Token)
                    );

                    return EvaluateWithSymbolAndParenthesisSubstitution (expressionList, 0);
                }
                catch
                {
                    return false;
                }
            }

            private bool EvaluateWithSymbolAndParenthesisSubstitution (List<Token> expressionList, int offset)
            {
                for (int index = offset; index < expressionList.Count; index++)
                {
                    var current = expressionList[index];
                    switch (current.LexicalElement)
                    {
                        case LexicalElements.Unknown:
                            SubstituteToken
                            (
                                expressionList,
                                index,
                                IsDefined (current.Text)
                            );
                            break;

                        case LexicalElements.Symbol_ParenthesisOpen:
                            SubstituteToken
                            (
                                expressionList,
                                index,
                                EvaluateWithSymbolAndParenthesisSubstitution (expressionList, index + 1)
                            );
                            break;

                        case LexicalElements.Symbol_ParenthesisClose:
                            var result = EvaluateWithNegationSubstitution (expressionList, offset, ref index);

                            expressionList.RemoveRange 
                            (
                                offset - 1,
                                (index - offset) + 1
                            );

                            return result;
                    }
                }

                int end = expressionList.Count;
                return EvaluateWithNegationSubstitution (expressionList, 0, ref end);
            }

            private bool EvaluateWithNegationSubstitution (List<Token> expressionList, int offset, ref int end)
            {
                for (int index = offset; index < end; index++)
                {
                    var current = expressionList[index];
                    if (current.LexicalElement != LexicalElements.Symbol_ExclamationMark)
                    {
                        continue;
                    }

                    if (end <= index + 1)
                    {
                        throw new ParseException ();
                    }

                    var next = expressionList[index + 1];
                    switch (next.LexicalElement)
                    {
                        case LexicalElements.True:
                            expressionList.RemoveAt (index);
                            SubstituteToken (expressionList, index, false);
                            end--;
                            break;

                        case LexicalElements.False:
                            expressionList.RemoveAt (index);
                            SubstituteToken (expressionList, index, true);
                            end--;
                            break;

                        case LexicalElements.Symbol_ExclamationMark:
                            expressionList.RemoveRange (index, 2);
                            end -= 2;
                            index--;
                            break;
                    }
                }

                return EvaluateWithOperatorSubstitution (expressionList, offset, ref end);
            }

            private bool EvaluateWithOperatorSubstitution (List<Token> expressionList, int offset, ref int end)
            {
                SubstituteOperator
                (
                    expressionList,
                    offset,
                    ref end,
                    LexicalElements.Symbol_Ampersand_Ampersand,
                    (a, b) => a && b
                );

                SubstituteOperator
                (
                    expressionList,
                    offset,
                    ref end,
                    LexicalElements.Symbol_Pipe_Pipe,
                    (a, b) => a || b
                );

                SubstituteOperator
                (
                    expressionList,
                    offset,
                    ref end,
                    LexicalElements.Symbol_EqualsTo_EqualsTo,
                    (a, b) => a == b
                );

                SubstituteOperator
                (
                    expressionList,
                    offset,
                    ref end,
                    LexicalElements.Symbol_ExclamationMark_EqualsTo,
                    (a, b) => a != b
                );

                if ((end - offset) != 1)
                {
                    throw new ParseException ();
                }

                return GetValue
                (
                    expressionList[offset]
                );
            }

            private void SubstituteOperator (List<Token> expressionList, int offset, ref int end, LexicalElements operatorElement, Func<bool, bool, bool> performOperator)
            {
                for (int index = offset; index < end - 2; index++)
                {
                    var left = expressionList[index];
                    var symbol = expressionList[index + 1];
                    var right = expressionList[index + 2];
                    if (symbol.LexicalElement != operatorElement)
                    {
                        continue;
                    }

                    expressionList.RemoveRange (index, 2);
                    end -= 2;

                    SubstituteToken
                    (
                        expressionList,
                        index,
                        performOperator
                        (
                            GetValue (left),
                            GetValue (right)
                        )
                    );
                }
            }

            private bool IsDefined (string symbol)
            {
                bool isDefined;
                if (localDefinedPreprocessorSymbols.TryGetValue (symbol, out isDefined))
                {
                    return isDefined;
                }

                return globalDefinedPreprocessorSymbols.Contains (symbol);
            }

            private bool GetValue (Token token)
            {
                switch (token.LexicalElement)
                {
                    case LexicalElements.True:
                        return true;

                    case LexicalElements.False:
                        return false;

                    default:
                        throw new ParseException ();
                }
            }

            private void SubstituteToken (List<Token> expressionList, int index, bool condition)
            {
                var substitutionToken = condition ? tokenTrue : tokenFalse;

                if (expressionList.Count <= index)
                {
                    throw new ParseException ();
                }

                expressionList[index] = substitutionToken;
            }


            private bool CurrentConditionHadPreviousInclusion
            {
                get
                {
                    if (preprocessorConditionStack.Count == 0)
                    {
                        throw new InvalidOperationException ("Empty stack");
                    }

                    return preprocessorConditionStack[preprocessorConditionStack.Count - 1];
                }
                set
                {
                    if (preprocessorConditionStack.Count == 0)
                    {
                        throw new InvalidOperationException ("Empty stack");
                    }

                    preprocessorConditionStack[preprocessorConditionStack.Count - 1] = value;
                }
            }
            #endregion


            #region -- Movement --
            public override bool MoveToNext ()
            {
                return MoveToNext (false);
            }

            public void MoveToNextExplicit ()
            {
                MoveToNext (true);
            }

            private bool MoveToNext (bool throwError)
            {
                if (base.MoveToNext ())
                {
                    ProcessSilentTokens (false);
                }

                if (HasNode)
                {
                    return true;
                }

                if (throwError)
                {
                    throw new ParseException ();
                }

                return false;
            }

            private void SkipToNextLine (Action action = null)
            {
                var errorLine = Token.Position.Line;

                while (base.MoveToNext () && (errorLine == Token.Position.Line))
                {
                    if (action != null)
                    {
                        action ();
                    }
                }
            }


            public bool SkipTo (params LexicalElements[] skipElements)
            {
                return SkipTo (false, skipElements);
            }

            public void SkipToExplicitly (params LexicalElements[] skipElements)
            {
                SkipTo (true, skipElements);
            }

            private bool SkipTo (bool throwError, params LexicalElements[] skipElements)
            {
                int parenthesisBracketCounter = 0;
                int squaredBracketCounter = 0;
                int curlyBracketCounter = 0;

                while (HasNode)
                {
                    if (HasSkippedTo (skipElements, LexicalElement, ref parenthesisBracketCounter, ref squaredBracketCounter, ref curlyBracketCounter))
                    {
                        return true;
                    }

                    MoveToNext (throwError);
                }

                return false;
            }

            public TokenNode PeekSkipOver (params LexicalElements[] skipElements)
            {
                int parenthesisBracketCounter = 0;
                int squaredBracketCounter = 0;
                int curlyBracketCounter = 0;

                var currentNode = Node;
                while (currentNode != null)
                {
                    if (HasSkippedTo (skipElements, LexicalElement, ref parenthesisBracketCounter, ref squaredBracketCounter, ref curlyBracketCounter))
                    {
                        return currentNode;
                    }

                    currentNode = currentNode.Next;
                }

                return null;
            }

            private static bool HasSkippedTo (LexicalElements[] skipElements, LexicalElements currentLexicalElement, ref int parenthesisBracketCounter, ref int squaredBracketCounter, ref int curlyBracketCounter)
            {
                switch (currentLexicalElement)
                {
                    case LexicalElements.Symbol_ParenthesisClose:
                        parenthesisBracketCounter--;
                        break;

                    case LexicalElements.Symbol_SquaredBracketClose:
                        squaredBracketCounter--;
                        break;

                    case LexicalElements.Symbol_CurlyBracketClose:
                        curlyBracketCounter--;
                        break;
                }

                if ((parenthesisBracketCounter == 0) && (squaredBracketCounter == 0) && (curlyBracketCounter == 0) && (skipElements.Any ((skipElement) => currentLexicalElement == skipElement)))
                {
                    return true;
                }

                switch (currentLexicalElement)
                {
                    case LexicalElements.Symbol_ParenthesisOpen:
                        parenthesisBracketCounter++;
                        break;

                    case LexicalElements.Symbol_SquaredBracketOpen:
                        squaredBracketCounter++;
                        break;

                    case LexicalElements.Symbol_CurlyBracketOpen:
                        curlyBracketCounter++;
                        break;
                }

                return false;
            }
            #endregion


            public void RollbackScopeAndSkipOver (params LexicalElements[] skipElements)
            {
                RollbackScope ();

                StartScope (ScopeTypes.None);

                SkipTo (false, skipElements);

                CommitScopeAndMoveToNext ();
            }


            public void IsLexicalElementExpected (LexicalElements expectedLexicalElement)
            {
                if (LexicalElement != expectedLexicalElement)
                {
                    throw new ParseException ();
                }
            }

            public bool IsLexicalElementMemberOf (LexicalElements lexicalGroup)
            {
                return (LexicalElement & lexicalGroup) != 0;
            }

            public bool HasReachedElementOrEndOfStream (LexicalElements endElement)
            {
                return !HasNode || (LexicalElement == endElement);
            }
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Netherlands Netherlands
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions