public CodeDOMDocumentServiceAHK(ProjectItemCodeDocument document) : base(document) { #region Create Language Master Root _root = new CodeTypeDeclarationEx(null, "Global") { CodeDocumentItem = document }; #region Create auto exec method for AHK Scripts _root.Members.Add(_autoexec = new CodeMemberMethodExAHK(true) { Name = "AutoExec", Attributes = MemberAttributes.Public | MemberAttributes.Static, DefiningType = _languageRoot, ReturnType = new CodeTypeReference(typeof(void)), LinePragma = new CodeLinePragma("all", 0), IsHidden = true, CodeDocumentItem = document }); #endregion #region Setup Base Object var baseobj = new CodeTypeDeclarationEx(null, "Object") { CodeDocumentItem = document, IsClass = true, IsBuildInType = true }; baseobj.Comments.Add(new CodeCommentStatement("Base Object of all other Custom Objects", true)); CodeMemberMethodExAHK method; #region Object.Insert method = new CodeMemberMethodExAHK(true) { Name = "Insert", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false, }; method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(object)), "key")); method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(object)), "value")); method.Comments.Add(new CodeCommentStatement("Inserts key-value pairs into the object, automatically adjusting existing keys if appropriate.", true)); method.ReturnType = new CodeTypeReference(typeof(bool)); baseobj.Members.Add(method); #endregion #region Object.Remove method = new CodeMemberMethodExAHK(true) { Name = "Remove", Project = document.Project, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(object)), "key")); method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(object)), "value")); method.Comments.Add(new CodeCommentStatement("Removes key-value pairs from an object.", true)); method.ReturnType = new CodeTypeReference(typeof(bool)); baseobj.Members.Add(method); #endregion #region Object.Clone method = new CodeMemberMethodExAHK(true) { Name = "Clone", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Comments.Add(new CodeCommentStatement("Returns a shallow copy of the object.", true)); method.ReturnType = new CodeTypeReference(typeof(object)); baseobj.Members.Add(method); #endregion #region Object.MinIndex method = new CodeMemberMethodExAHK(true) { Name = "MinIndex", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Comments.Add(new CodeCommentStatement("If any integer keys are present, MinIndex returns the lowest and MaxIndex returns the highest. Otherwise an empty string is returned.", true)); method.ReturnType = new CodeTypeReference(typeof(int)); baseobj.Members.Add(method); #endregion #region Object.MaxIndex method = new CodeMemberMethodExAHK(true) { Name = "MaxIndex", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Comments.Add(new CodeCommentStatement("If any integer keys are present, MinIndex returns the lowest and MaxIndex returns the highest. Otherwise an empty string is returned.", true)); method.ReturnType = new CodeTypeReference(typeof(int)); baseobj.Members.Add(method); #endregion #region Object.SetCapacity method = new CodeMemberMethodExAHK(true) { Name = "SetCapacity", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(int)), "MaxItemsOrKey")); method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(int)), "ByteSize")); method.Comments.Add(new CodeCommentStatement("If any integer keys are present, MinIndex returns the lowest and MaxIndex returns the highest. Otherwise an empty string is returned.", true)); method.ReturnType = new CodeTypeReference(typeof(int)); baseobj.Members.Add(method); #endregion #region Object.GetCapacity method = new CodeMemberMethodExAHK(true) { Name = "GetCapacity", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(int)), "Key")); method.Comments.Add(new CodeCommentStatement("Returns the current capacity of an object or one of its fields.", true)); method.ReturnType = new CodeTypeReference(typeof(int)); baseobj.Members.Add(method); #endregion #region Object.HasKey method = new CodeMemberMethodExAHK(true) { Name = "HasKey", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(int)), "Key")); method.Comments.Add(new CodeCommentStatement("Returns true if Key is associated with a value (even '') within Object, otherwise false.", true)); method.ReturnType = new CodeTypeReference(typeof(bool)); baseobj.Members.Add(method); #endregion #region Object._NewEnum method = new CodeMemberMethodExAHK(true) { Name = "_NewEnum", CodeDocumentItem = document, IsDefaultMethodInvoke = true, IsTraditionalCommand = false }; method.Comments.Add(new CodeCommentStatement("Returns a new enumerator to enumerate this object's key-value pairs.", true)); method.ReturnType = new CodeTypeReference(typeof(IEnumerator)); baseobj.Members.Add(method); #endregion _root.Members.Add(baseobj); #endregion // Import build-in members foreach(var m in document.CodeLanguage.BuildInMembers) { var codeobj = m as ICodeMemberEx; if(codeobj != null) { codeobj.Project = document.Project; } _root.Members.Add(m); } #endregion _rootLanguageSnapshot = _root; }
protected async Task CompileTokenFile(CancellationToken cancellationToken) { try { await TaskEx.Run(() => { _languageRoot = new CodeTypeDeclarationRoot() { Project = _document.Project }; CodeTypeDeclarationEx initialparent = _languageRoot; cancellationToken.ThrowIfCancellationRequested(); _dependingOnSave = this.DependingOn; #region Clean Up _document.Project.Solution.ErrorService.ClearAllErrorsFrom(_document, Errors.ErrorSource.ASTParser); _codeRangeManager.Clear(); #endregion #region Merge DependingOn Members if(_dependingOnSave == null) { // merge super base members _languageRoot.Members.AddRange(_root.Members); firstAfterNull = true; } else { //if(!_project.IsInUpdate) { // if(firstAfterNull) { // ignoreDependingOnce = true; // _dependingOnSave.CompileTokenFileAsync(); // firstAfterNull = false; // } // _dependingOnSave.WaitUntilUpdated(200); //} _languageRoot.Members.AddRange(_dependingOnSave.GetRootTypeSnapshot().Members); } #endregion var codeLineMap = _document.SegmentService.GetCodeSegmentLinesMap(); CodeTypeDeclaration parent = initialparent; Stack<CodeSegment> paramstack = new Stack<CodeSegment>(); int linecnt = 0; if(codeLineMap.Keys.Any()) linecnt = codeLineMap.Keys.Max(); CodeTokenLine line; Stack<CodeTypeDeclarationEx> parentHirarchy = new Stack<CodeTypeDeclarationEx>(); int bcc = 0; parentHirarchy.Push(initialparent); cancellationToken.ThrowIfCancellationRequested(); #region Parse for(int i = 0; i <= linecnt; i++) { cancellationToken.ThrowIfCancellationRequested(); if(codeLineMap.ContainsKey(i)) line = codeLineMap[i]; else continue; // is class definition?: #region Parse Class Definition var classkeywordSegment = line.CodeSegments[0].ThisOrNextOmit(whitespacetokenNewLines); if(classkeywordSegment != null && classkeywordSegment.Token == Token.KeyWord && classkeywordSegment.TokenString.Equals("class", StringComparison.CurrentCultureIgnoreCase)) { var classNameSegment = classkeywordSegment.FindNextOnSameLine(Token.Identifier); if(classNameSegment != null) { var next = classNameSegment.NextOmit(whitespacetokenNewLines); if(next != null) { CodeTypeDeclarationEx thisparent = parentHirarchy.Any() ? parentHirarchy.Peek() : _languageRoot; CodeTypeReferenceEx basecls = null; CodeSegment refBaseClass = null; if(next.Token == Token.KeyWord && next.TokenString.Equals("extends", StringComparison.InvariantCultureIgnoreCase)) { refBaseClass = next.NextOmit(whitespacetokenNewLines); if(refBaseClass != null) { if(refBaseClass.Token == Token.Identifier) { refBaseClass.CodeDOMObject = basecls = new CodeTypeReferenceEx(_document, refBaseClass.TokenString, thisparent); next = refBaseClass.NextOmit(whitespacetokenNewLines); } else { RegisterError(_document, next.Next, "Expected: Class Name Identifier"); next = next.NextOmit(whitespacetokenNewLines); } } else { if(next.Next != null && next.Next.Token != Token.BlockOpen) { RegisterError(_document, next.Next, "Expected: Class Name Identifier"); next = next.NextOmit(whitespacetokenNewLines); } } } if(next != null) { if(next.Token == Token.BlockOpen) { #region Add Class Declaration CodeSegment classBodyStart = next; var type = new CodeTypeDeclarationEx(_document, classNameSegment.TokenString) { IsClass = true, LinePragma = CreatePragma(classNameSegment, _document.FilePath), CodeDocumentItem = _document }; classNameSegment.CodeDOMObject = type; // check if this type was alread defined in this scope if(thisparent.GetInheritedMembers().Contains(type)) { RegisterError(_document, classNameSegment, "oh my dear, this class already exisits in the current scope!"); } else { #region Check & Resolve Baseclass if(basecls != null) { //check if we have a circual interhance tree var baseclassImpl = basecls.ResolveTypeDeclarationCache(); if(baseclassImpl != null && baseclassImpl.IsSubclassOf(new CodeTypeReferenceEx(_document, classNameSegment.TokenString, thisparent))) { //circular dependency detected!! RegisterError(_document, refBaseClass, "Woops you just produced a circular dependency in your inheritance tree!"); } else { if(basecls != null) type.BaseTypes.Add(basecls); else type.BaseTypes.Add(new CodeTypeReferenceEx(_document, "Object", thisparent) { ResolvedTypeDeclaration = _superBase }); } } #endregion // extract class documentation Comment var comment = ExtractComment(classkeywordSegment); if(comment != null) type.Comments.Add(comment); // Add it to the CodeDOM Tree thisparent.Members.Add(type); type.Parent = thisparent; } // Create a CodeRange Item int startOffset = classBodyStart.Range.Offset; var classBodyEnd = classBodyStart.FindClosingBracked(true); if(classBodyEnd != null) { int length = (classBodyEnd.Range.Offset - startOffset); _codeRangeManager.Add(new CodeRange(new SimpleSegment(startOffset, length), type)); } else { RegisterError(_document, classBodyStart, "Expected: " + Token.BlockClosed); } parentHirarchy.Push(type); bcc++; i = classBodyStart.LineNumber; // jumt to: class Foo { * <---| continue; #endregion } else { RegisterError(_document, next, "Expected: " + Token.BlockOpen); i = (next.Next != null) ? next.Next.LineNumber : next.LineNumber; } } } } } #endregion // adjust some asumptions the tokenizer has made if(parentHirarchy.Count > 1) { // if we are in a class body, we can't have a traditional command invoke foreach(var s in line.CodeSegments) if(s.Token == Token.TraditionalCommandInvoke) { s.Token = Token.Identifier; break; } } // is method definition?: #region Analyze for Method Definition var methodSegment = line.CodeSegments[0].ThisOrNextOmit(whitespacetokenNewLines); if(methodSegment != null && methodSegment.Token == Token.Identifier) { var methodSignatureStart = methodSegment.Next; if(methodSignatureStart != null && methodSignatureStart.Token == Token.LiteralBracketOpen) { var methodSignatureEnd = methodSignatureStart.FindClosingBracked(false); if(methodSignatureEnd != null) { var startMethodBody = methodSignatureEnd.NextOmit(whitespacetokenNewLinesComments); if(startMethodBody != null && startMethodBody.Token == Token.BlockOpen) { // jup we have a method definition here. // Method body starts at startMethodBody CodeTypeDeclarationEx thisparent = parentHirarchy.Any() ? parentHirarchy.Peek() : _languageRoot; bool hasDeclarationError = false; #region Generate Method Definition DOM var method = new CodeMemberMethodExAHK(_document) { Name = methodSegment.TokenString, LinePragma = CreatePragma(methodSegment, _document.FilePath), CodeDocumentItem = _document, ReturnType = new CodeTypeReferenceEx(_document, typeof(object)) }; methodSegment.CodeDOMObject = method; //check if this method is not already defined elsewere in current scope var equalmethods = from m in thisparent.Members.Cast<CodeTypeMember>() let meth = m as CodeMemberMethodExAHK where meth != null && !meth.IsBuildInType && meth.Equals(method) select meth; if(equalmethods.Any()) { RegisterError(_document, methodSegment, string.Format("The Methodename '{0}' is already used in the current scope!", method.Name)); hasDeclarationError = true; } else { // extract Method Comment var comment = ExtractComment(methodSegment); if(comment != null) method.Comments.Add(comment); // extract method params paramstack.Clear(); CodeSegment previous = methodSignatureStart; // get method params: while(true) { var current = previous.Next; if(current.Token == Token.Identifier || current.Token == Token.KeyWord) { paramstack.Push(current); } else if(current.Token == Token.ParameterDelemiter || current.Token == Token.LiteralBracketClosed) { // end of param reached: if(paramstack.Count == 1) { // thread one param as the untyped argument, type of Object method.Parameters.Add(new CodeParameterDeclarationExpressionEx(typeof(object), paramstack.Pop().TokenString) { Direction = FieldDirection.In }); } else if(paramstack.Count == 2) { CodeParameterDeclarationExpressionEx param; var second = paramstack.Pop(); var first = paramstack.Pop(); //handle byref -> if(first.Token == Token.KeyWord && first.TokenString.Equals(BY_REF, StringComparison.InvariantCultureIgnoreCase)) { param = new CodeParameterDeclarationExpressionEx(typeof(object), second.TokenString); param.Direction = FieldDirection.Ref; } else { param = new CodeParameterDeclarationExpressionEx( new CodeTypeReferenceEx(_document, first.TokenString, thisparent), paramstack.Pop().TokenString); } // thread two param as the type and argument method.Parameters.Add(param); } if(current.Token == Token.LiteralBracketClosed) break; } else if(current.Token == Token.NewLine){ break; } previous = current; } } #endregion // Method body ends at var endMethodBody = startMethodBody.FindClosingBracked(true); if(endMethodBody != null) { // get method statements method.Statements.AddRange( CollectAllCodeStatements(cancellationToken, thisparent, codeLineMap, startMethodBody.LineNumber + 1, endMethodBody.LineNumber)); // add it to the code DOM Tree if(!hasDeclarationError) { thisparent.Members.Add(method); method.DefiningType = thisparent; } // Create a CodeRange Item int startOffset = startMethodBody.Range.Offset; int length = (endMethodBody.Range.Offset - startOffset); _codeRangeManager.Add(new CodeRange(new SimpleSegment(startOffset, length), method)); // move the scanpointer to the method end: i = endMethodBody.LineNumber; continue; } else { RegisterError(_document, startMethodBody, "Missing: " + Token.BlockClosed); } } } } } #endregion // is class property / field #region Parse Class Properties / Fields if(parentHirarchy.Count > 1) { // we must be in a class to have method properties // extract keywords & Identifiers untill we reach // any otherToken - omit whitespaces List<CodeSegment> declarationStack = new List<CodeSegment>(); CodeSegment current = line.CodeSegments[0].ThisOrNextOmit(whitespacetokens); while(current != null) { if(current.Token == Token.WhiteSpace) { //ignore white spaces } else if(current.Token == Token.Identifier || current.Token == Token.KeyWord) { declarationStack.Add(current); } else { break; } current = current.Next; } switch(declarationStack.Count) { case 0: break; case 1: // instance field definition if(declarationStack[0].Token == Token.Identifier) { // this is an untyped instance class field declaration var propertyType = new CodeTypeReference(typeof(object)); var memberprop = new CodeMemberPropertyEx(_document) { Name = declarationStack[0].TokenString, Attributes = MemberAttributes.Public, Type = propertyType, LinePragma = CreatePragma(declarationStack[0], _document.FilePath) }; declarationStack[0].CodeDOMObject = memberprop; parentHirarchy.Peek().Members.Add(memberprop); } else { RegisterError(_document, declarationStack[0], string.Format("Unexpected Token: {0}", declarationStack[0].Token)); } break; case 2: // we have two members in the decalration stack // expected token flows // keyword - identifier: static Field, int Field // identifier - identifier: Car myTypedCarField // for the time beeing, AHK just supports static Field if(declarationStack[0].Token == Token.KeyWord && declarationStack[0].TokenString.Equals(MODIFIER_STATIC, StringComparison.InvariantCultureIgnoreCase)) { if(declarationStack[1].Token == Token.Identifier) { // this is an untyped static class field declaration var propertyType = new CodeTypeReference(typeof(object)); var memberprop = new CodeMemberPropertyEx(_document) { Name = declarationStack[1].TokenString, Attributes = MemberAttributes.Public | MemberAttributes.Static, Type = propertyType, LinePragma = CreatePragma(declarationStack[1], _document.FilePath) }; declarationStack[1].CodeDOMObject = memberprop; parentHirarchy.Peek().Members.Add(memberprop); } else { RegisterError(_document, declarationStack[0], string.Format("Expected: {0}", Token.Identifier)); } } else { RegisterError(_document, declarationStack[0], string.Format("Unexpected '{0}' in Class Body", declarationStack[0].TokenString)); } break; default: for(int ci = 0; ci < declarationStack.Count; ci++) { RegisterError(_document, declarationStack[ci], string.Format("To much declaration Members", declarationStack[0].Token)); } break; } } #endregion #region Parse Remaining Tokens if(codeLineMap.ContainsKey(i)) { var lineBlock = codeLineMap[i]; CodeTypeDeclarationEx thisparent = parentHirarchy.Any() ? parentHirarchy.Peek() : _languageRoot; foreach(var segment in lineBlock.CodeSegments) { cancellationToken.ThrowIfCancellationRequested(); if(segment.Token == Token.BlockOpen) { bcc++; } else if(segment.Token == Token.BlockClosed) { bcc--; if(parentHirarchy.Count - 2 == bcc) { if(parentHirarchy.Any()) parentHirarchy.Pop(); } } } _autoexec.Statements.AddRange( CollectAllCodeStatements(cancellationToken, thisparent, codeLineMap, i, i)); } else continue; #endregion } #endregion AnalyzeAST(cancellationToken); lock(_rootLanguageSnapshotLOCK) { _rootLanguageSnapshot = _languageRoot; } }); } catch(OperationCanceledException) { throw; } }