private void _executeForeach(ForeachExpression expr, List <Line> relatedLines, Line baseLine) { var contents = relatedLines.Select(l => l.Content).ToArray(); var iterateThose = expr.Arguments.Arguments.Select(parseExpr).ToList(); unpackPackedArguments(); //get smallest index and iterate it. var min = iterateThose.Min(i => i.Count); var vars = Context.Variables; for (int i = 0; i < min; i++) { //set variables if (expr.Depth > 0) { vars[$"i{expr.Depth}"] = new NumberScalar(i); } else { vars["i"] = new NumberScalar(i); } for (int j = 0; j < iterateThose.Count; j++) { vars[$"__{j + 1 + expr.Depth * 100}__"] = iterateThose[j][i]; } var variables = new List <string>(); //a list of all added variables that will be cleaned after this i iteration. //now here we iterate contents and set all variables in it. for (var contentIndex = 0; contentIndex < contents.Length; contentIndex++) { var content = contents[contentIndex]; //iterate lines, one at a time // ReSharper disable once RedundantToStringCall var copy = content.ToString().Replace("|#", "#"); bool changed = false; int last_access_index = 0; const string HashtagExpressionRegex = @"(?<!\\)\#\((?:[^()]|(?<open>\()|(?<-open>\)))+(?(open)(?!))\)"; var hashtagExprs = Regex.Matches(copy, HashtagExpressionRegex, Regexes.DefaultRegexOptions).Cast <Match>().ToArray(); //replace all emit commands copy = ExpressionLexer.ReplaceRegex(copy, @"(?<!\\)\#([0-9]+)", match => { var key = $"__{match.Groups[1].Value}__"; if (hashtagExprs.Any(m => m.IsMatchNestedTo(match))) { //it is inside hashtagExpr #(...) return(key); } return(_emit(vars[key])); }); var ew = new ExpressionWalker(ExpressionLexer.Tokenize(copy, ExpressionToken.StringLiteral)); if (ew.HasNext) { do { _restart: if (changed) { changed = false; var cleanedCopy = new string(' ', last_access_index) + copy.Substring(last_access_index); ew = new ExpressionWalker(ExpressionLexer.Tokenize(cleanedCopy, ExpressionToken.StringLiteral)); } var current = ew.Current; //iterate all tokens of that line if (current.Token == ExpressionToken.Mod && ew.HasNext) { if (ew.HasBack && ew.PeakBack.Token == ExpressionToken.Escape) { continue; } var expr_ew = new ExpressionWalker(ExpressionLexer.Tokenize(copy.Substring(current.Match.Index))); //var offset = current.Match.Index; //var hashtag = expr_ew.Current; current = expr_ew.NextToken(); switch (current.Token) { case ExpressionToken.Foreach: var code = contents.SkipWhile(s => s != content).StringJoin(); var e = ForeachExpression.Parse(code); var foreachExpr = (ForeachExpression)e.Related[0]; foreachExpr.Depth = expr.Depth + 1; //no need to mark lines from e for deletion, they are already marked beforehand. _executeForeach(foreachExpr, e.RelatedLines, baseLine); contentIndex += e.RelatedLines.Count + 2 - 1; //first for the %foreach line, second for the closer %, -1 because we increment index by one on next iteration. goto _skipline; default: continue; } } if (current.Token == ExpressionToken.Hashtag && ew.HasNext) { if (ew.HasBack && ew.PeakBack.Token == ExpressionToken.Escape) { continue; } var offset = current.Match.Index; var expr_ew = new ExpressionWalker(ExpressionLexer.Tokenize(copy.Substring(current.Match.Index))); var hashtag = expr_ew.Current; current = expr_ew.NextToken(); switch (current.Token) { case ExpressionToken.Literal: //this is variable declaration %varname = expr var peak = expr_ew.PeakNext.Token; if (peak == ExpressionToken.Equal) { var e = VariableDeclarationExpression.Parse(expr_ew); var varname = e.Name.AsString(); if (!Context.Variables.ContainsKey(varname)) { variables.Add(varname); } CompileAction(new ParserAction(ParserToken.Declaration, new List <Expression>() { e }), new OList <ParserAction>(0)); goto _skipline; } break; case ExpressionToken.LeftParen: { //it is an expression. expr_ew.NextOrThrow(); var expression = Expression.ParseExpression(expr_ew); object val = EvaluateObject(expression, baseLine); if (val is ReferenceData rd) //make sure references are unpacked { val = rd.UnpackReference(Context); } expr_ew.IsCurrentOrThrow(ExpressionToken.RightParen); var emit = val is Data d?d.Emit() : val.ToString(); copy = copy .Remove(offset + hashtag.Match.Index, expr_ew.Current.Match.Index + 1 - hashtag.Match.Index) .Insert(offset + hashtag.Match.Index, emit); last_access_index = hashtag.Match.Index + emit.Length; changed = true; goto _restart; } case ExpressionToken.NumberLiteral: { if (expr_ew.HasNext && expr_ew.PeakNext.Token == ExpressionToken.LeftBracet) { //it is an indexer call. //todo indexer } else { //it is a simple emit var key = $"#{expr_ew.Current.Match.Value}"; object val = vars[$"__{expr_ew.Current.Match.Value}__"]; copy = Regex.Replace(copy, Regex.Escape(key), _emit(val)); changed = true; } goto _restart; } default: continue; } } //incase it is escaped, continue. } while (ew.Next()); } _nextline: //cleanup escapes copy = copy.Replace("\\#", "#"); baseLine.ReplaceOrAppend(copy + (copy.EndsWith("\n") ? "" : "\n")); _skipline :; } foreach (var variable in variables) { Context.Variables.Remove(variable); } } if (expr.Depth == 0) { Context.Variables.Remove("i"); } else { Context.Variables.Remove($"i{expr.Depth}"); } for (var i = 0; i < iterateThose.Count; i++) { Context.Variables.Remove($"__{i + 1 + expr.Depth * 100}__"); } if (!baseLine.ContentWasModified) { baseLine.MarkedForDeletion = true; } IList parseExpr(Expression arg) { var ev = EvaluateObject(arg, baseLine); if (ev is ReferenceData d) { ev = d.UnpackReference(Context); } if (ev is StringScalar ss) { return(ss.ToCharArray()); } if (ev is NetObject no) { ev = no.Value; } return((IList)ev); } void unpackPackedArguments() { //unpack PackedArguments for (var i = iterateThose.Count - 1; i >= 0; i--) { if (iterateThose[i] is PackedArguments pa) { iterateThose.InsertRange(i, pa.Objects.Select(o => (IList)o)); } } iterateThose.RemoveAll(it => it is PackedArguments); } }