public ASContext() { features = new ContextFeatures(); completionCache = new CompletionCache(this, null); cacheRefreshTimer = new Timer(); cacheRefreshTimer.Interval = 1500; // delay initial refresh cacheRefreshTimer.Tick += new EventHandler(cacheRefreshTimer_Tick); }
/// <summary> /// Look if the provided text starts with any declaration keyword /// </summary> public static bool IsDeclaration(string line, ContextFeatures features) { foreach (string keyword in features.accessKeywords) if (line.StartsWith(keyword) && IsSpaceAt(line, keyword.Length)) return true; foreach (string keyword in features.declKeywords) if (line.StartsWith(keyword) && IsSpaceAt(line, keyword.Length)) return true; return false; }
/// <summary> /// Look if the provided text starts with any declaration keyword /// </summary> public static bool IsDeclaration(string line, ContextFeatures features) { foreach (string keyword in features.accessKeywords) { if (line.StartsWith(keyword) && IsSpaceAt(line, keyword.Length)) { return(true); } } foreach (string keyword in features.declKeywords) { if (line.StartsWith(keyword) && IsSpaceAt(line, keyword.Length)) { return(true); } } return(false); }
public ASFileParser() { features = new ContextFeatures(); }
public void ParseSrc(FileModel fileModel, string ba, bool allowBaReExtract) { //TraceManager.Add("Parsing " + Path.GetFileName(fileModel.FileName)); model = fileModel; model.OutOfDate = false; model.CachedModel = false; if (model.Context != null) features = model.Context.Features; if (features != null && features.hasModules) model.Module = Path.GetFileNameWithoutExtension(model.FileName); // pre-filtering if (allowBaReExtract && model.HasFiltering && model.Context != null) ba = model.Context.FilterSource(fileModel.FileName, ba); model.InlinedIn = null; model.InlinedRanges = null; // language features model.Imports.Clear(); model.Classes.Clear(); model.Members.Clear(); model.Namespaces.Clear(); model.Regions.Clear(); model.PrivateSectionIndex = 0; model.Package = ""; model.MetaDatas = null; // state int len = ba.Length; if (len < 0) return; int i = 0; line = 0; // when parsing cache file including multiple files resetParser: char c1; char c2; int matching = 0; bool isInString = false; int inString = 0; int braceCount = 0; bool inCode = true; // comments char[] commentBuffer = new char[COMMENTS_BUFFER]; int commentLength = 0; lastComment = null; curComment = null; // tokenisation tryPackage = true; hasPackageSection = false; haXe = model.haXe; TypeCommentUtils.ObjectType = haXe ? "Dynamic" : "Object"; version = (haXe) ? 4 : 1; curToken = new Token(); prevToken = new Token(); int tokPos = 0; int tokLine = 0; curMethod = null; curMember = null; valueKeyword = null; valueMember = null; curModifiers = 0; curNamespace = "internal"; curAccess = 0; char[] buffer = new char[TOKEN_BUFFER]; int length = 0; char[] valueBuffer = new char[VALUE_BUFFER]; int valueLength = 0; int paramBraceCount = 0; int paramTempCount = 0; int paramParCount = 0; int paramSqCount = 0; bool hadWS = true; bool hadDot = false; inParams = false; inEnum = false; inTypedef = false; inAbstract = false; inValue = false; inConst = false; inType = false; inGeneric = false; inAnonType = false; bool addChar = false; int evalToken = 0; //bool evalKeyword = true; context = 0; modifiers = 0; foundColon = false; bool handleDirectives = features.hasDirectives || cachedPath != null; bool inlineDirective = false; while (i < len) { c1 = ba[i++]; isInString = (inString > 0); /* MATCH COMMENTS / STRING LITERALS */ switch (matching) { // look for comment block/line and preprocessor commands case 0: if (!isInString) { // new comment if (c1 == '/' && i < len) { c2 = ba[i]; if (c2 == '/') { // Check if this this is a /// comment if (i + 1 < len && ba[i + 1] == '/') { // This is a /// comment matching = 4; isBlockComment = true; i++; } else { // This is a regular comment matching = 1; isBlockComment = false; } inCode = false; i++; continue; } else if (c2 == '*') { isBlockComment = (i + 1 < len && ba[i + 1] == '*'); matching = 2; inCode = false; i++; while (i < len - 1) { c2 = ba[i]; if (c2 == '*' && ba[i + 1] != '/') i++; else break; } continue; } } // don't look for comments in strings else if (c1 == '"') { isInString = true; inString = 1; } else if (c1 == '\'') { isInString = true; inString = 2; } // preprocessor statements else if (c1 == '#' && handleDirectives) { int ls = i - 2; inlineDirective = false; while (ls > 0) { c2 = ba[ls--]; if (c2 == 10 || c2 == 13) break; else if (c2 > 32) { inlineDirective = true; break; } } c2 = ba[i]; if (i < 2 || ba[i - 2] < 33 && c2 >= 'a' && c2 <= 'z') { matching = 3; inCode = false; continue; } } } // end of string else if (isInString) { if (c1 == '\\') { i++; continue; } else if (c1 == 10 || c1 == 13) inString = 0; else if ((inString == 1) && (c1 == '"')) inString = 0; else if ((inString == 2) && (c1 == '\'')) inString = 0; // extract "include" declarations if (inString == 0 && length == 7 && context == 0) { string token = new string(buffer, 0, length); if (token == "include") { string inc = ba.Substring(tokPos, i - tokPos); if (model.MetaDatas == null) model.MetaDatas = new List<ASMetaData>(); ASMetaData meta = new ASMetaData("Include"); meta.ParseParams(inc); model.MetaDatas.Add(meta); } } } break; // skip commented line case 1: if (c1 == 10 || c1 == 13) { // ignore single comments commentLength = 0; inCode = true; matching = 0; } break; // skip commented block case 2: if (c1 == '*') { bool end = false; while (i < len) { c2 = ba[i]; if (c2 == '\\') { i++; continue; } if (c2 == '/') { end = true; break; } else if (c2 == '*') i++; else break; } if (end) { lastComment = (commentLength > 0) ? new string(commentBuffer, 0, commentLength) : null; // TODO parse for TODO statements? commentLength = 0; inCode = true; matching = 0; i++; continue; } } break; // directive/preprocessor statement case 3: if (c1 == 10 || c1 == 13 || (inlineDirective && c1 <= 32)) { if (commentLength > 0) { string directive = new string(commentBuffer, 0, commentLength); if (directive.StartsWith("if")) { inCode = true; } else if (directive.StartsWith("else")) { inCode = true; } else if (directive.StartsWith("end")) { inCode = true; // directive end matching = 0; } else inCode = true; // FD cache custom directive if (cachedPath != null && directive.StartsWith("file-cache ")) { // parsing done! FinalizeModel(); // next model string realFile = directive.Substring(11); FileModel newModel = model.Context != null ? model.Context.CreateFileModel(realFile) : new FileModel(realFile); newModel.LastWriteTime = cacheLastWriteTime; newModel.CachedModel = true; if (features != null && features.hasModules) newModel.Module = Path.GetFileNameWithoutExtension(realFile); haXe = newModel.haXe; if (!cachedPath.HasFile(realFile) && File.Exists(realFile)) { newModel.OutOfDate = (File.GetLastWriteTime(realFile) > cacheLastWriteTime); cachedPath.AddFile(newModel); } model = newModel; goto resetParser; // loop } } else inCode = true; commentLength = 0; matching = 0; } break; // We are inside a /// comment case 4: { bool end = false; bool skipAhead = false; // See if we just ended a line if (2 <= i && (ba[i - 2] == 10 || ba[i - 2] == 13)) { // Check ahead to the next line, see if it has a /// comment on it too. // If it does, we want to continue the comment with that line. If it // doesn't, then this comment is finished and we will set end to true. for (int j = i + 1; j < len; ++j) { // Skip whitespace char twoBack = ba[j - 2]; if (' ' != twoBack && '\t' != twoBack) { if ('/' == twoBack && '/' == ba[j - 1] && '/' == ba[j]) { // There is a comment ahead. Move up to it so we can gather the // rest of the comment i = j + 1; skipAhead = true; break; } else { // Not a comment! We're done! end = true; break; } } } } if (end) { // The comment is over and we want to write it out lastComment = (commentLength > 0) ? new string(commentBuffer, 0, commentLength).Trim() : null; commentLength = 0; inCode = true; matching = 0; // Back up i so we can start gathering comments from right after the line break --i; continue; } if (skipAhead) { // We just hit another /// and are skipping up to right after it. continue; } break; } } /* LINE/COLUMN NUMBER */ if (c1 == 10 || c1 == 13) { if (cachedPath == null) line++; // cache breaks line count if (c1 == 13 && i < len && ba[i] == 10) i++; } /* SKIP CONTENT */ if (!inCode) { // store comments if (matching == 2 || (matching == 3 && handleDirectives) || matching == 4) { if (commentLength < COMMENTS_BUFFER) commentBuffer[commentLength++] = c1; } else if (matching == 1 && (c1 == '#' || c1 == '{')) { commentBuffer[commentLength++] = c1; while (i < len) { c2 = ba[i]; if (commentLength < COMMENTS_BUFFER) commentBuffer[commentLength++] = c2; if (c2 == 10 || c2 == 13) break; i++; } string comment = new String(commentBuffer, 0, commentLength); Match match = ASFileParserRegexes.Region.Match(comment); if (match.Success) { string regionName = match.Groups["name"].Value.Trim(); MemberModel region = new MemberModel(regionName, String.Empty, FlagType.Declaration, Visibility.Default); region.LineFrom = region.LineTo = line; model.Regions.Add(region); } } continue; } else if (isInString) { // store parameter default value if (inValue && valueLength < VALUE_BUFFER) valueBuffer[valueLength++] = c1; continue; } if (braceCount > 0 && !inValue) { if (c1 == '/') { LookupRegex(ref ba, ref i); } else if (c1 == '}') { lastComment = null; braceCount--; if (braceCount == 0 && curMethod != null) { curMethod.LineTo = line; curMethod = null; } } else if (c1 == '{') braceCount++; // escape next char else if (c1 == '\\') i++; continue; } /* PARSE DECLARATION VALUES/TYPES */ if (inValue) { bool stopParser = false; bool valueError = false; if (inType && !inAnonType && !inGeneric && !Char.IsLetterOrDigit(c1) && ".{}-><".IndexOf(c1) < 0) { inType = false; inValue = false; inGeneric = false; valueLength = 0; length = 0; context = 0; } else if (c1 == '{') { if (!inType || valueLength == 0 || valueBuffer[valueLength - 1] == '<' || paramBraceCount > 0) { paramBraceCount++; stopParser = true; } } else if (c1 == '}') { if (paramBraceCount > 0) { paramBraceCount--; stopParser = true; } else valueError = true; } else if (c1 == '(') { paramParCount++; stopParser = true; } else if (c1 == ')') { if (paramParCount > 0) { paramParCount--; stopParser = true; } else valueError = true; } else if (c1 == '[') paramSqCount++; else if (c1 == ']') { if (paramSqCount > 0) { paramSqCount--; stopParser = true; } else valueError = true; } else if (c1 == '<') { if (i > 1 && ba[i - 2] == '<') paramTempCount = 0; // a << b else { if (inType) inGeneric = true; paramTempCount++; } } else if (c1 == '>') { if (ba[i - 2] == '-') { /*haxe method signatures*/ } else if (paramTempCount > 0) { paramTempCount--; stopParser = true; } else valueError = true; } else if (c1 == '/') { int i0 = i; if (LookupRegex(ref ba, ref i) && valueLength < VALUE_BUFFER - 3) { valueBuffer[valueLength++] = '/'; for (; i0 < i; i0++) if (valueLength < VALUE_BUFFER - 2) valueBuffer[valueLength++] = ba[i0]; valueBuffer[valueLength++] = '/'; continue; } } else if (inValue && (inParams || inType || inConst) && c1 == '/' && valueLength == 0) // lookup native regex { int itemp = i; valueBuffer[valueLength++] = '/'; while (valueLength < VALUE_BUFFER && i < len) { c1 = ba[i++]; if (c1 == '\n' || c1 == '\r') { valueLength = 0; i = itemp; break; } valueBuffer[valueLength++] = c1; if (c1 == '\\' && i < len) { c1 = ba[i++]; valueBuffer[valueLength++] = c1; } else if (c1 == '/') break; } } else if ((c1 == ':' || c1 == ',') && paramBraceCount > 0) stopParser = true; // end of value if ((valueError || (!stopParser && paramBraceCount == 0 && paramParCount == 0 && paramSqCount == 0 && paramTempCount == 0)) && (c1 == ',' || c1 == ';' || c1 == '}' || c1 == '\r' || c1 == '\n' || (inParams && c1 == ')') || inType)) { if (!inType && (!inValue || c1 != ',')) { length = 0; context = 0; } inValue = false; inGeneric = false; //if (valueLength < VALUE_BUFFER) valueBuffer[valueLength++] = c1; } // in params, store the default value else if ((inParams || inType || inConst) && valueLength < VALUE_BUFFER) { if (c1 <= 32) { if (valueLength > 0 && valueBuffer[valueLength - 1] != ' ') valueBuffer[valueLength++] = ' '; } else valueBuffer[valueLength++] = c1; } // detect keywords if (!Char.IsLetterOrDigit(c1)) { // escape next char if (c1 == '\\' && i < len) { c1 = ba[i++]; if (valueLength < VALUE_BUFFER) valueBuffer[valueLength++] = c1; continue; } if (stopParser) continue; else if (valueError && c1 == ')') inValue = false; else if (inType && inGeneric && (c1 == '<' || c1 == '.')) continue; else if (inAnonType) continue; hadWS = true; } } // store type / parameter value if (!inValue && valueLength > 0) { string param = /*(valueBuffer[0] == '{' && valueBuffer[0] != '[') ? "..." :*/ new string(valueBuffer, 0, valueLength); // get text before the last keyword found if (valueKeyword != null) { int p = param.LastIndexOf(valueKeyword.Text); if (p > 0) param = param.Substring(0, p).TrimEnd(); } if (curMember == null) { if (inType) { prevToken.Text = curToken.Text; prevToken.Line = curToken.Line; prevToken.Position = curToken.Position; curToken.Text = param; curToken.Line = tokLine; curToken.Position = tokPos; EvalToken(true, true/*false*/, i - 1 - valueLength); evalToken = 0; } } else if (inType) { foundColon = false; if (haXe) { if (param.EndsWith("}") || param.Contains(">")) { param = ASFileParserRegexes.Spaces.Replace(param, ""); param = param.Replace(",", ", "); param = param.Replace("->", " -> "); } } curMember.Type = param; } // AS3 const or method parameter's default value else if (version > 2 && (curMember.Flags & FlagType.Variable) > 0) { if (inParams || inConst) curMember.Value = param; curMember.LineTo = line; if (c1 == '\r' || c1 == '\n') curMember.LineTo--; if (inConst && c1 != ',') { context = 0; inConst = false; } } // valueLength = 0; valueMember = null; if (!inParams && !(inConst && context != 0) && c1 != '{') continue; else length = 0; } /* TOKENIZATION */ // whitespace if (c1 <= 32) { hadWS = true; continue; } // a dot can be in an identifier if (c1 == '.') { if (length > 0 || (inParams && version == 3)) { hadWS = false; hadDot = true; addChar = true; if (!inValue && context == FlagType.Variable && !foundColon) { bool keepContext = inParams && (length == 0 || buffer[0] == '.'); if (!keepContext) context = 0; } } else continue; } else { // should we evaluate the token? if (hadWS && !hadDot && !inGeneric && length > 0) { evalToken = 1; } hadWS = false; hadDot = false; bool shortcut = true; // valid char for keyword if (c1 >= 'a' && c1 <= 'z') { addChar = true; } else { // valid chars for identifiers if (c1 >= 'A' && c1 <= 'Z') { addChar = true; } else if (c1 == '$' || c1 == '_') { addChar = true; } else if (length > 0) { if (c1 >= '0' && c1 <= '9') { addChar = true; } else if (c1 == '*' && context == FlagType.Import) { addChar = true; } // AS3/haXe generics else if (features.hasGenerics && c1 == '<') { if (!inValue && i > 2 && length > 1 && i < len - 3 && Char.IsLetterOrDigit(ba[i - 3]) && (Char.IsLetter(ba[i]) || (haXe && ba[i] == '{')) && (Char.IsLetter(buffer[0]) || buffer[0] == '_')) { if (curMember == null) { evalToken = 0; if (inGeneric) paramTempCount++; else { paramTempCount = 1; inGeneric = true; } addChar = true; } else { evalToken = 0; inGeneric = true; inValue = true; inType = true; inAnonType = false; valueLength = 0; for (int j = 0; j < length; j++) valueBuffer[valueLength++] = buffer[j]; valueBuffer[valueLength++] = c1; length = 0; paramBraceCount = 0; paramParCount = 0; paramSqCount = 0; paramTempCount = 1; continue; } } } else if (inGeneric && (c1 == ',' || c1 == '.' || c1 == '-' || c1 == '>' || c1 == ':')) { hadWS = false; hadDot = false; evalToken = 0; if (!inValue) { addChar = true; if (c1 == '>' && inGeneric) { if (paramTempCount > 0) paramTempCount--; if (paramTempCount == 0 && paramBraceCount == 0 && paramSqCount == 0 && paramParCount == 0) inGeneric = false; } } } else { evalToken = 2; shortcut = false; } } // star is valid in import statements else if (c1 == '*' && version == 3) { addChar = true; } // conditional haXe parameter else if (c1 == '?' && haXe && inParams && length == 0) { addChar = true; } else shortcut = false; } // eval this word if (evalToken > 0) { prevToken.Text = curToken.Text; prevToken.Line = curToken.Line; prevToken.Position = curToken.Position; curToken.Text = new string(buffer, 0, length); curToken.Line = tokLine; curToken.Position = tokPos; EvalToken(!inValue, (c1 != '=' && c1 != ','), i - 1 - length); length = 0; evalToken = 0; } if (!shortcut) // start of block if (c1 == '{') { if (context == FlagType.Package || context == FlagType.Class) // parse package/class block { context = 0; } else if (context == FlagType.Enum) // parse enum block { if (curClass != null && (curClass.Flags & FlagType.Enum) > 0) inEnum = true; else { context = 0; curModifiers = 0; braceCount++; // ignore block } } else if (context == FlagType.TypeDef) // parse typedef block { if (curClass != null && (curClass.Flags & FlagType.TypeDef) > 0) { inTypedef = true; if (i < len && ba[i] == '>') { buffer[0] = 'e'; buffer[1] = 'x'; buffer[2] = 't'; buffer[3] = 'e'; buffer[4] = 'n'; buffer[5] = 'd'; buffer[6] = 's'; length = 7; context = FlagType.Class; } } else { context = 0; curModifiers = 0; braceCount++; // ignore block } } else if (context == FlagType.Abstract) // parse abstract block { if (curClass != null && (curClass.Flags & FlagType.Abstract) > 0) inAbstract = true; else { context = 0; curModifiers = 0; braceCount++; // ignore block } } else if (foundColon && haXe && length == 0) // copy haXe anonymous type { inValue = true; inType = true; inAnonType = true; valueLength = 0; valueBuffer[valueLength++] = c1; paramBraceCount = 1; paramParCount = 0; paramSqCount = 0; paramTempCount = 0; continue; } else if (foundConstant) // start config block { flattenNextBlock++; foundConstant = false; context = 0; } else if (ScriptMode) // not in a class, parse if/for/while/do blocks { context = 0; } else braceCount++; // ignore block } // end of block else if (c1 == '}') { curComment = null; foundColon = false; foundConstant = false; if (flattenNextBlock > 0) // content of this block was parsed { flattenNextBlock--; } // outside of a method, the '}' ends the current class else if (curClass != null) { if (curClass != null) curClass.LineTo = line; curClass = null; inEnum = false; inTypedef = false; inAbstract = false; } else { if (hasPackageSection && model.PrivateSectionIndex == 0) model.PrivateSectionIndex = line + 1; } } // member type declaration else if (c1 == ':' && !inValue && !inGeneric) { foundColon = curMember != null && curMember.Type == null; // recognize compiler config block if (!foundColon && braceCount == 0 && i < len - 2 && ba[i] == ':' && Char.IsLetter(ba[i + 1])) foundConstant = true; } // next variable declaration else if (c1 == ',') { if ((context == FlagType.Variable || context == FlagType.TypeDef) && curMember != null) { curAccess = curMember.Access; foundKeyword = FlagType.Variable; foundColon = false; lastComment = null; } else if (context == FlagType.Class && prevToken.Text == "implements") { curToken.Text = "implements"; foundKeyword = FlagType.Implements; } } else if (c1 == '(') { if (!inValue && context == FlagType.Variable && curToken.Text != "catch" && (!haXe || curToken.Text != "for")) if (haXe && curMember != null && valueLength == 0) // haXe properties { curMember.Flags -= FlagType.Variable; curMember.Flags |= FlagType.Getter | FlagType.Setter; context = FlagType.Function; } else context = 0; // beginning of method parameters if (context == FlagType.Function) { context = FlagType.Variable; inParams = true; inGeneric = false; if (valueMember != null && curMember == null) { valueLength = 0; //valueMember.Flags -= FlagType.Variable; ??? valueMember.Flags = FlagType.Function; curMethod = curMember = valueMember; valueMember = null; } else if (curMember == null) { context = FlagType.Function; if ((curModifiers & FlagType.Getter) > 0) { curModifiers -= FlagType.Getter; EvalToken(true, false, i); curMethod = curMember; context = FlagType.Variable; } else if ((curModifiers & FlagType.Setter) > 0) { curModifiers -= FlagType.Setter; EvalToken(true, false, i); curMethod = curMember; context = FlagType.Variable; } else { inParams = false; context = 0; } } else { curMethod = curMember; } } // an Enum value with parameters else if (inEnum && curToken != null) { context = FlagType.Variable; inParams = true; curMethod = curMember ?? new MemberModel(); curMethod.Name = curToken.Text; curMethod.Flags = curModifiers | FlagType.Function | FlagType.Static; curMethod.Parameters = new List<MemberModel>(); // if (curClass != null && curMember == null) curClass.Members.Add(curMethod); } // a TypeDef method with parameters else if (inTypedef && curToken != null) { context = FlagType.Variable; inParams = true; curMethod = curMember ?? new MemberModel(); curMethod.Name = curToken.Text; curMethod.Flags = curModifiers | FlagType.Function; curMethod.Parameters = new List<MemberModel>(); // if (curClass != null && curMember == null) curClass.Members.Add(curMethod); } // an Abstract "opaque type" else if (context == FlagType.Abstract && prevToken.Text == "abstract") { foundKeyword = FlagType.Class; curModifiers = FlagType.Extends; } else if (curMember == null && curToken.Text != "catch" && (!haXe || curToken.Text != "for")) { context = 0; inGeneric = false; } } // end of statement else if (c1 == ';') { context = (inEnum) ? FlagType.Enum : 0; inGeneric = false; modifiers = 0; inParams = false; curMember = null; } // end of method parameters else if (c1 == ')' && inParams) { context = 0; if (inEnum) context = FlagType.Enum; else if (inTypedef) context = FlagType.TypeDef; modifiers = 0; inParams = false; curMember = curMethod; } // skip value of a declared variable else if (c1 == '=') { if (context == FlagType.Variable || (context == FlagType.Enum && inEnum)) { if (!inValue && curMember != null) { inValue = true; inConst = (curMember.Flags & FlagType.Constant) > 0; inType = false; inGeneric = false; paramBraceCount = 0; paramParCount = 0; paramSqCount = 0; paramTempCount = 0; valueLength = 0; valueMember = curMember; } } } // metadata else if (!inValue && c1 == '[') { if (version == 3) LookupMeta(ref ba, ref i); else if (features.hasCArrays && curMember != null && curMember.Type != null) { if (ba[i] == ']') curMember.Type = features.CArrayTemplate + "@" + curMember.Type; } } // haXe signatures: T -> T -> T else if (haXe && c1 == '-' && curMember != null) { if (ba[i] == '>' && curMember.Type != null) { curMember.Type += " ->"; foundColon = true; } } // escape next char else if (c1 == '\\') { i++; continue; } // literal regex else if (c1 == '/' && version == 3) { if (LookupRegex(ref ba, ref i)) continue; } } // put in buffer if (addChar) { if (length < TOKEN_BUFFER) buffer[length++] = c1; if (length == 1) { tokPos = i - 1; tokLine = line; } addChar = false; } } // parsing done! FinalizeModel(); // post-filtering if (cachedPath == null && model.HasFiltering && model.Context != null) model.Context.FilterSource(model); // Debug.WriteLine("out model: " + model.GenerateIntrinsic(false)); }