public bool Update(Tokenizer tokenizer, IEnumerable <int> affectedLines) { var expressionTokenizer = new ExpressionTokenizer(tokenizer, null); int groupStart = 0; // we can ignore everything before the first modified line if (affectedLines.Any()) { var nextUpdatedLine = affectedLines.Min(); LOG_UPDATE("Updating lines {0}-{1} (searching {2} groups)", nextUpdatedLine, affectedLines.Max(), Groups.Count); while (groupStart < Groups.Count && nextUpdatedLine > Groups[groupStart].LastLine) { ++groupStart; } if (groupStart < Groups.Count) { LOG_UPDATE("Found line {0} in group {1} (first line of group is {2})", nextUpdatedLine, groupStart, Groups[groupStart].FirstLine); nextUpdatedLine = Math.Min(Groups[groupStart].FirstLine, nextUpdatedLine); } expressionTokenizer.PushState(); expressionTokenizer.AdvanceToLine(nextUpdatedLine); if (groupStart > 0) { // if the first character to be parsed is not a valid identifier character or comment token, // the new content might need to be merged with the previous group. bool needPreviousGroup; if (Char.IsWhiteSpace(expressionTokenizer.NextChar)) { expressionTokenizer.PushState(); expressionTokenizer.SkipWhitespace(); needPreviousGroup = !IsAtValidGroupStart(expressionTokenizer); expressionTokenizer.PopState(); } else { needPreviousGroup = !IsAtValidGroupStart(expressionTokenizer); } if (needPreviousGroup) { --groupStart; nextUpdatedLine = Groups[groupStart].FirstLine; LOG_UPDATE("Also processing group {0} (first line of group is {1})", groupStart, nextUpdatedLine); expressionTokenizer.PopState(); expressionTokenizer.AdvanceToLine(nextUpdatedLine); } } } else { LOG_UPDATE("Updating all lines ({0} groups)", Groups.Count); } LOG_GROUPS(groupStart - 2, groupStart + 2); // parse whatever is remaining var newGroups = new List <ExpressionGroup>(); ParseGroups(expressionTokenizer, newGroups); // attempt to match the end of the script int groupStop = Groups.Count; int newGroupStop = newGroups.Count; if (newGroupStop > 0) { while (groupStop > groupStart) { var existingGroup = Groups[--groupStop]; var newGroup = newGroups[--newGroupStop]; if (!existingGroup.ExpressionsMatch(newGroup)) { ++groupStop; ++newGroupStop; break; } var firstLine = existingGroup.FirstLine; var lastLine = existingGroup.LastLine; existingGroup.ReplaceExpressions(newGroup, false); Scope.UpdateVariables(existingGroup.Modifies, newGroup); var adjustment = existingGroup.FirstLine - firstLine; if (adjustment != 0) { if (existingGroup.GeneratedAchievements != null) { foreach (var achievement in existingGroup.GeneratedAchievements) { achievement.SourceLine += adjustment; } } if (existingGroup.GeneratedLeaderboards != null) { foreach (var leaderboard in existingGroup.GeneratedLeaderboards) { leaderboard.SourceLine += adjustment; } } foreach (var error in _evaluationErrors) { var innerError = error; while (innerError != null) { if (innerError.Location.Start.Line >= firstLine && innerError.Location.End.Line <= lastLine) { innerError.AdjustLines(adjustment); } innerError = innerError.InnerError; } } } if (newGroupStop == 0) { if (groupStop == groupStart) { // no change detected return(false); } // groups were removed break; } } } // whatever is remaining will be swapped out. // capture any affected variables and remove associated evaluation errors var affectedVariables = new HashSet <string>(); for (int i = groupStart; i < groupStop; ++i) { var group = Groups[i]; foreach (var variable in group.Modifies) { affectedVariables.Add(variable); Scope.UndefineVariable(variable); Scope.UndefineFunction(variable); } for (int j = _evaluationErrors.Count - 1; j >= 0; j--) { var error = _evaluationErrors[j]; if (error.Location.End.Line >= group.FirstLine && error.Location.Start.Line <= group.LastLine) { _evaluationErrors.RemoveAt(j); } else if (error.InnerError != null) { error = error.InnermostError; if (error.Location.End.Line >= group.FirstLine && error.Location.Start.Line <= group.LastLine) { _evaluationErrors.RemoveAt(j); } } } } // also capture any affected variables for groups being swapped in, and determine // if they need to be evaluated. for (int i = 0; i < newGroupStop; ++i) { var newGroup = newGroups[i]; newGroup.UpdateMetadata(); newGroup.MarkForEvaluation(); foreach (var variable in newGroup.Modifies) { affectedVariables.Add(variable); } } // perform the swap if (newGroupStop == 0) { if (groupStart < Groups.Count) { LOG_UPDATE("Removing groups {0}-{1} (lines {2}-{3})", groupStart, groupStop - 1, Groups[groupStart].FirstLine, Groups[groupStop - 1].LastLine); Groups.RemoveRange(groupStart, groupStop - groupStart); } } else if (groupStop == groupStart) { LOG_UPDATE("Adding {0} groups (lines {1}-{2})", newGroupStop, newGroups[0].FirstLine, newGroups[newGroupStop - 1].LastLine); Groups.InsertRange(groupStart, newGroups.Take(newGroupStop)); } else { LOG_UPDATE("Replacing groups {0}-{1} (lines {2}-{3}) with {4} groups (lines {5}-{6})", groupStart, groupStop - 1, Groups[groupStart].FirstLine, Groups[groupStop - 1].LastLine, newGroupStop, newGroups[0].FirstLine, newGroups[newGroupStop - 1].LastLine); Groups.RemoveRange(groupStart, groupStop - groupStart); Groups.InsertRange(groupStart, newGroups.Take(newGroupStop)); } LOG_GROUPS(groupStart - 2, groupStart + newGroupStop + 2); bool needsEvaluated = false; // re-evaluate any groups that are dependent on (or modify) the affected variables if (affectedVariables.Count > 0) { FlagDependencies(affectedVariables); needsEvaluated = true; } else { for (int i = 0; i < newGroupStop; ++i) { if (newGroups[i].NeedsEvaluated) { needsEvaluated = true; break; } } } return(needsEvaluated); }