diff --git a/src/DotWeb.Decompiler/Core/BackEnd.cs b/src/DotWeb.Decompiler/Core/BackEnd.cs deleted file mode 100644 index c8db49e..0000000 --- a/src/DotWeb.Decompiler/Core/BackEnd.cs +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright 2009, Frank Laub -// -// This file is part of DotWeb. -// -// DotWeb is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// DotWeb is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with DotWeb. If not, see . - -using System; -using System.Collections.Generic; -using System.Linq; -using DotWeb.Decompiler.CodeModel; -using Mono.Cecil.Cil; -using System.Diagnostics; - -namespace DotWeb.Decompiler.Core -{ - class BackEnd - { - public ControlFlowGraph Cfg { get; private set; } - public CodeMethodMember Method { get; private set; } - - class Context - { - private ControlFlowGraph cfg; - public Node LatchNode { get; private set; } - public Node IfFollow { get; private set; } - public Node LoopFollow { get; private set; } - public Node LoopHeader { get; private set; } - - public Context(ControlFlowGraph cfg) { - this.cfg = cfg; - this.LatchNode = null; - this.IfFollow = null; - this.LoopHeader = null; - this.LoopFollow = null; - } - - public Context Clone() { - return new Context(this.cfg) { - LatchNode = this.LatchNode, - IfFollow = this.IfFollow, - LoopFollow = this.LoopFollow, - LoopHeader = this.LoopHeader - }; - } - - public Context NewIfFollow(int ifFollow) { - var node = this.cfg.DepthFirstPostOrder[ifFollow]; - var ctx = this.Clone(); - ctx.IfFollow = node; - return ctx; - } - - public Context NewLatch(Node latchNode) { - var ctx = this.Clone(); - ctx.LatchNode = latchNode; - return ctx; - } - - public Context NewLoop(int loopHeader, int loopFollow) { - var ctx = this.Clone(); - ctx.LoopHeader = this.cfg.DepthFirstPostOrder[loopHeader]; - ctx.LoopFollow = this.cfg.DepthFirstPostOrder[loopFollow]; - return ctx; - } - } - - public BackEnd(ControlFlowGraph cfg) { - this.Cfg = cfg; - - this.Method = new CodeMethodMember(cfg.Method) { - ExternalMethods = cfg.ExternalMethods - }; - - //foreach (LocalVariableInfo local in cfg.Method.GetMethodBody().LocalVariables) { - // this.Method.Statements.Add(new CodeVariableDeclarationStatement( - // local.LocalType, - // CodeDomGenerator.LocalVariable(local.LocalIndex) - // )); - //} - } - - public void WriteCode() { - this.WriteCode(this.Cfg.Root, new Context(this.Cfg), this.Method.Statements); - } - - private void WriteIf(BasicBlock bb, Context context, List stmts) { - int i = 0; - for (i = 0; i < bb.Statements.Count - 1; i++) { - stmts.Add(bb.Statements[i]); - } - - CodeStatement last = bb.Statements[i]; - CodeExpressionStatement ces = last as CodeExpressionStatement; - CodeExpression test = ces.Expression; - CodeIfStatement condition = new CodeIfStatement(); - stmts.Add(condition); - - bool emptyThen = false; - // is there a follow? - if (bb.IfFollow != Node.NoNode) { - // process the THEN part - Node succ = bb.ThenEdge; - if (succ.DfsTraversed != DfsTraversal.Alpha) { - // not visited - context = context.NewIfFollow(bb.IfFollow); - if (succ.DfsPostOrder != bb.IfFollow) { - // THEN part - test = test.Invert(); - WriteCode((BasicBlock)succ, context, condition.TrueStatements); - } - else { - // empty THEN part => negate ELSE part - WriteCode((BasicBlock)bb.ElseEdge, context, condition.TrueStatements); - emptyThen = true; - } - } - - // process the ELSE part - succ = bb.ElseEdge; - if (succ.DfsTraversed != DfsTraversal.Alpha) { - if (succ.DfsPostOrder != bb.IfFollow) { - // ELSE part - WriteCode((BasicBlock)succ, context.NewIfFollow(bb.IfFollow), condition.FalseStatements); - } - } - else if (!emptyThen) { - // already visited => emit label - throw new InvalidOperationException(); - } - - // Continue with the follow - succ = this.Cfg.DepthFirstPostOrder[bb.IfFollow]; - if (succ.DfsTraversed != DfsTraversal.Alpha) { - WriteCode((BasicBlock)succ, context, stmts); - } - } - else { - // no follow => if..then..else - test = test.Invert(); - WriteCode((BasicBlock)bb.ThenEdge, context, condition.TrueStatements); - WriteCode((BasicBlock)bb.ElseEdge, context, condition.FalseStatements); - } - - condition.Condition = test; - } - - private void WriteLoopInner(BasicBlock bb, Context context, List stmts) { - Node succ; - if (bb.LoopType == LoopType.While) { - succ = bb.ThenEdge; - if (succ.DfsPostOrder == bb.LoopFollow) { - succ = bb.ElseEdge; - } - } - else { - succ = bb.Successors.First(); - } - - if (succ.DfsTraversed != DfsTraversal.Alpha) { - WriteCode((BasicBlock)succ, context.NewLatch(bb.LatchNode), stmts); - } - } - - private void WriteLoopFollow(BasicBlock bb, Context context, List stmts) { - Node succ = this.Cfg.DepthFirstPostOrder[bb.LoopFollow]; - if (succ.DfsTraversed != DfsTraversal.Alpha) { - WriteCode((BasicBlock)succ, context, stmts); - } - } - - private void WriteWhileLoop(BasicBlock bb, Context context, List stmts) { - context = context.NewLoop(bb.LoopHead, bb.LoopFollow); - - CodeWhileStatement loop; - if (bb.Statements.Count > 1) { - // emit a while(true) { $bb.stmts; if($condition) { $then_stmts; break; } ... } - loop = new CodeWhileStatement { - TestExpression = new CodePrimitiveExpression(true) - }; - - WriteIf(bb, context, loop.Statements); - } - else { - CodeStatement last = bb.Statements.Last(); - CodeExpressionStatement ces = last as CodeExpressionStatement; - CodeExpression test = ces.Expression; - - if (bb.ElseEdge.DfsPostOrder == bb.LoopFollow) { - test = test.Invert(); - } - - // emit a pre-tested loop - loop = new CodeWhileStatement { - TestExpression = test - }; - } - - stmts.Add(loop); - - if (bb != bb.LatchNode) { - WriteLoopInner(bb, context, loop.Statements); - } - - if (bb.LoopFollow != Node.NoNode) { - WriteLoopFollow(bb, context, stmts); - } - } - - private void WriteRepeatLoop(BasicBlock bb, Context context, List stmts) { - List temp = new List(); - int i = 0; - for (i = 0; i < bb.Statements.Count - 1; i++) { - temp.Add(bb.Statements[i]); - } - CodeStatement last = bb.Statements[i]; - CodeExpressionStatement ces = last as CodeExpressionStatement; - CodeExpression test = ces.Expression; - CodeRepeatStatement loop = new CodeRepeatStatement { - TestExpression = test - }; - - stmts.Add(loop); - - if (bb != bb.LatchNode) { - WriteLoopInner(bb, context, loop.Statements); - } - - loop.Statements.AddRange(temp); - - if (bb.LoopFollow != Node.NoNode) { - WriteLoopFollow(bb, context, stmts); - } - } - - private void WriteLoop(BasicBlock bb, Context context, List stmts) { - switch (bb.LoopType) { - case LoopType.While: - WriteWhileLoop(bb, context, stmts); - break; - case LoopType.Repeat: - WriteRepeatLoop(bb, context, stmts); - break; - case LoopType.Endless: - Debug.Assert(false); - //loop = new CodeWhileStatement { - // TestExpression = new CodePrimitiveExpression(true) - //}; - break; - default: - throw new InvalidOperationException(); - } - } - - private void WriteCode(BasicBlock bb, Context context, List stmts) { - if ((context.IfFollow != null) && (bb == context.IfFollow)) { - return; - } - if (bb.DfsTraversed == DfsTraversal.Alpha) { - return; - } - bb.DfsTraversed = DfsTraversal.Alpha; - - if (bb.LoopType != LoopType.None) { - WriteLoop(bb, context, stmts); - } - else if (bb.IsTwoWay) { - WriteIf(bb, context, stmts); - } - else if (bb.FlowControl == FlowControl.Return || - bb.FlowControl == FlowControl.Throw || - bb == context.LatchNode) { - WriteBasicBlock(bb, context, stmts); - return; - } - else if (bb.IsMultiWay) { - WriteCases(bb, context, stmts); - } - else { - WriteBasicBlock(bb, context, stmts); - Node succ = bb.Successors.First(); - if (succ.DfsTraversed != DfsTraversal.Alpha) { - WriteCode((BasicBlock)succ, context, stmts); - } - } - } - - private void WriteCases(BasicBlock bb, Context context, List stmts) { - int i = 0; - for (i = 0; i < bb.Statements.Count - 1; i++) { - stmts.Add(bb.Statements[i]); - } - - CodeStatement last = bb.Statements[i]; - CodeSwitchStatement sw = last as CodeSwitchStatement; - stmts.Add(sw); - - var cases = (Instruction[])bb.LastInstruction.Operand; - Dictionary ccDict = new Dictionary(); - foreach (BasicBlock succ in bb.Successors) { - CodeCase cc = new CodeCase(); - if (succ.DfsTraversed != DfsTraversal.Alpha) { - WriteCode(succ, context.NewIfFollow(bb.CaseTail), cc.Statements); - } - ccDict.Add(succ.BeginOffset, cc); - } - - for (int j = 0; j < cases.Length; j++) { - int offset = cases[j].Offset; - CodeCase cc = ccDict[offset]; - cc.Expressions.Add(new CodePrimitiveExpression(j)); - } - - sw.Cases.AddRange(ccDict.Values); - - if (bb.CaseTail != Node.NoNode) { - /* Continue with the follow */ - Node next = this.Cfg.DepthFirstPostOrder[bb.CaseTail]; - if (next.DfsTraversed != DfsTraversal.Alpha) { - WriteCode((BasicBlock)next, context, stmts); - } - } - } - - private void WriteBasicBlock(BasicBlock bb, Context context, List stmts) { - foreach (CodeStatement stmt in bb.Statements) { - var gotoStmt = stmt as CodeGotoStatement; - if (gotoStmt != null) { - if (context.LoopFollow != null) { - var bbFollow = (BasicBlock)context.LoopFollow; - if (bbFollow.FirstInstruction == gotoStmt.Target) { - stmts.Add(new CodeBreakStatement()); - continue; - } - } - if (context.LoopHeader != null) { - var bbHeader = (BasicBlock)context.LoopHeader; - if (bbHeader.FirstInstruction == gotoStmt.Target) { - stmts.Add(new CodeContinueStatement()); - continue; - } - } - } - else { - stmts.Add(stmt); - } - } - } - } -} diff --git a/src/DotWeb.Decompiler/Core/ControlFlowAnalyzer.cs b/src/DotWeb.Decompiler/Core/ControlFlowAnalyzer.cs deleted file mode 100644 index 61bd4a4..0000000 --- a/src/DotWeb.Decompiler/Core/ControlFlowAnalyzer.cs +++ /dev/null @@ -1,426 +0,0 @@ -// Copyright 2009, Frank Laub -// -// This file is part of DotWeb. -// -// DotWeb is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// DotWeb is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with DotWeb. If not, see . - -using System; -using System.Collections.Generic; -using System.Linq; -using DotWeb.Decompiler.CodeModel; -using Mono.Cecil.Cil; -using DotWeb.Utility; - -namespace DotWeb.Decompiler.Core -{ - class ControlFlowAnalyzer - { - public ControlFlowGraph Cfg { get; private set; } - - public ControlFlowAnalyzer(ControlFlowGraph cfg) { - this.Cfg = cfg; - } - - public void Structure() { - //CompoundConditions(); - - FindImmediateDominator(); - if (Cfg.HasCases) { - StructureCases(); - } - StructureLoops(); - StructureIfs(); - - //DisplayDfs(this.Cfg.Root); - } - - private void DisplayDfs(BasicBlock bb) { - bb.DfsTraversed = DfsTraversal.Display; - - Console.WriteLine("{0}: NodeType: {1}, in: {2}, out: {3}", - bb.RefName, bb.FlowControl, bb.Predecessors.Count, bb.Successors.Count); - Console.WriteLine("dfsFirst: {0}, dfsLast: {1}, immedDom: {2}", - bb.DfsPreOrder, bb.DfsPreOrder, bb.ImmediateDominator); - - string latch = "(null)"; - if (bb.LatchNode != null) - latch = bb.LatchNode.RefName; - Console.WriteLine("LoopType: {0}, LoopHead: {1}, LatchNode: {2}, Follow: {3}", - bb.LoopType, bb.LoopHead, latch, bb.LoopFollow); - Console.WriteLine("IfFollow: {0}", - bb.IfFollow); - Console.WriteLine("----"); - - foreach (Node node in bb.Successors) { - if (node.IsInvalid) - continue; - - if (node.DfsTraversed != DfsTraversal.Display) - DisplayDfs((BasicBlock)node); - } - } - - /// - /// Finds the immediate dominator of each node in the graph pProc->cfg. - /// Adapted version of the dominators algorithm by Hecht and Ullman; finds - /// immediate dominators only. - /// Note: graph should be reducible - /// - private void FindImmediateDominator() { - Node[] dfsList = this.Cfg.DepthFirstPostOrder; - for (int curIndex = 0; curIndex < dfsList.Length; curIndex++) { - Node node = dfsList[curIndex]; - if (node.IsInvalid) - continue; - - foreach (Node pred in node.Predecessors) { - int predIndex = pred.DfsPostOrder; - if (predIndex < curIndex) { - node.ImmediateDominator = CommonDominator(node.ImmediateDominator, predIndex); - } - } - } - } - - /// - /// Finds the common dominator of the current immediate dominator - /// currImmDom and its predecessor's immediate dominator predImmDom - /// - private int CommonDominator(int curImmDom, int predImmDom) { - if (curImmDom == Node.NoDominator) - return predImmDom; - if (predImmDom == Node.NoDominator) - return curImmDom; - while ((curImmDom != Node.NoDominator) && (predImmDom != Node.NoDominator) && - (curImmDom != predImmDom)) { - if (curImmDom < predImmDom) - predImmDom = this.Cfg.DepthFirstPostOrder[predImmDom].ImmediateDominator; - else - curImmDom = this.Cfg.DepthFirstPostOrder[curImmDom].ImmediateDominator; - } - return curImmDom; - } - - private bool IsSuccessor(int successor, int header) - { - return Cfg.DepthFirstPostOrder[header].Successors.Any(x => x.DfsPostOrder == successor); - } - - private void StructureCases() { - int exitNode = Node.NoNode; - List caseNodes = new List(); - - /* Linear scan of the nodes in reverse dfsLast order, searching for - * case nodes */ - for (int i = Cfg.DepthFirstPostOrder.Length - 1; i >= 0; i--) { - BasicBlock caseHeader = (BasicBlock)Cfg.DepthFirstPostOrder[i]; - if (caseHeader.IsInvalid) - continue; - - if (caseHeader.LastInstruction.OpCode != OpCodes.Switch) - continue; - - /* Find descendant node which has as immediate predecessor - * the current header node, and is not a successor. */ - for (int j = i + 2; j < Cfg.DepthFirstPostOrder.Length; j++) { - var node = Cfg.DepthFirstPostOrder[j]; - if (node.IsInvalid) - continue; - - if (!IsSuccessor(j, i) && node.ImmediateDominator == i) { - if (exitNode == Node.NoNode) { - exitNode = j; - } - else if (Cfg.DepthFirstPostOrder[exitNode].Predecessors.Count < node.Predecessors.Count) { - exitNode = j; - } - } - } - caseHeader.CaseTail = exitNode; - - /* Tag nodes that belong to the case by recording the - * header field with caseHeader. */ - - caseNodes.Add(i); - caseHeader.CaseHead = i; - foreach (BasicBlock node in caseHeader.Successors) { - TagNodesInCase(node, caseNodes, i, exitNode); - } - if (exitNode != Node.NoNode) { - Cfg.DepthFirstPostOrder[exitNode].CaseHead = i; - } - } - } - - /// - /// Recursive procedure to tag nodes that belong to the case described by - /// the list l, head and tail (dfsLast index to first and exit node of the case). - /// - /// - /// - /// - /// - private void TagNodesInCase(BasicBlock node, ICollection list, int head, int tail) { - node.DfsTraversed = DfsTraversal.Case; - int current = node.DfsPostOrder; - if ((current != tail) && - (node.LastInstruction.OpCode != OpCodes.Switch) && - list.Contains(node.ImmediateDominator)) { - list.Add(current); - node.CaseHead = head; - foreach (BasicBlock bb in node.Successors) { - if (bb.DfsTraversed != DfsTraversal.Case) { - TagNodesInCase(bb, list, head, tail); - } - } - } - } - - /// - /// Checks if the edge (p,s) is a back edge. If node s was visited first - /// during the dfs traversal (ie. s has a smaller dfsFirst number) or s == p, - /// then it is a backedge. - /// Also incrementes the number of backedges entries to the header node. - /// - /// - /// - /// - private bool IsBackEdge(Node pred, Node succ) { - if (pred.DfsPreOrder >= succ.DfsPreOrder) { - succ.BackEdgeCount++; - return true; - } - return false; - } - - private void StructureLoops() { - foreach (var graph in this.Cfg.Sequences) { - foreach (Interval interval in graph.Nodes) { - StructureLoopForInterval(interval); - } - } - } - - private void StructureLoopForInterval(Interval interval) { - // Find nodes that belong to the interval (nodes from G1) - List nodes = new List(); - interval.CollectNodes(nodes); - BasicBlock head = (BasicBlock)nodes.First(); - - BasicBlock latchNode = null; - // Find greatest enclosing back edge (if any) - foreach (BasicBlock pred in head.Predecessors) { - if (nodes.Contains(pred) && IsBackEdge(pred, head)) { - if (latchNode == null) { - latchNode = pred; - } - else { - if (pred.DfsPostOrder > latchNode.DfsPostOrder) - latchNode = pred; - } - } - } - - /* Find nodes in the loop and the type of loop */ - if (latchNode != null) { - // Check latching node is at the same nesting level of case - // statements (if any) and that the node doesn't belong to - // another loop. - if ((latchNode.CaseHead == head.CaseHead) && - (latchNode.LoopHead == Node.NoNode)) { - head.LatchNode = latchNode; - FindNodesInLoop(latchNode, head, nodes); - latchNode.IsLatchNode = true; - } - } - } - - /// - /// Flags nodes that belong to the loop determined by (latchNode, head) and - /// determines the type of loop. - /// - /// - /// - /// - private void FindNodesInLoop(BasicBlock latchNode, BasicBlock headerNode, List nodes) { - headerNode.LoopHead = headerNode.DfsPostOrder; - - List loopNodes = new List(); - loopNodes.Add(headerNode.LoopHead); - - for (int i = headerNode.LoopHead + 1; i < latchNode.DfsPostOrder; i++) { - Node node = this.Cfg.DepthFirstPostOrder[i]; - if (node.IsInvalid) - continue; - - int immedDom = node.ImmediateDominator; - if (loopNodes.Contains(immedDom) && nodes.Contains(node)) { - loopNodes.Add(i); - if (node.LoopHead == Node.NoNode) { - node.LoopHead = headerNode.LoopHead; - } - } - } - - latchNode.LoopHead = headerNode.LoopHead; - if (latchNode != headerNode) { - loopNodes.Add(latchNode.DfsPostOrder); - } - - ClassifyLoop(headerNode, latchNode, loopNodes); - } - - private void ClassifyLoop(BasicBlock headerNode, BasicBlock latchNode, List loopNodes) { - int thenDfs = headerNode.ThenEdge.DfsPostOrder; - int elseDfs = Node.NoNode; - if (headerNode.Successors.Count > 1) - elseDfs = headerNode.ElseEdge.DfsPostOrder; - - if (latchNode.IsTwoWay) { - if ((headerNode.IsTwoWay) || (latchNode == headerNode)) { - if ((latchNode == headerNode) || - loopNodes.Contains(thenDfs) && - loopNodes.Contains(elseDfs)) { - headerNode.LoopType = LoopType.Repeat; - if (latchNode.ThenEdge == headerNode) - headerNode.LoopFollow = latchNode.ElseEdge.DfsPostOrder; - else - headerNode.LoopFollow = latchNode.ThenEdge.DfsPostOrder; - latchNode.IsLoopNode = true; - } - else { - headerNode.LoopType = LoopType.While; - if (loopNodes.Contains(thenDfs)) - headerNode.LoopFollow = elseDfs; - else - headerNode.LoopFollow = thenDfs; - headerNode.IsLoopNode = true; - } - } - else { - // head = anything besides 2-way, latch = 2-way - headerNode.LoopType = LoopType.Repeat; - if (latchNode.ThenEdge == headerNode) - headerNode.LoopFollow = latchNode.ElseEdge.DfsPostOrder; - else - headerNode.LoopFollow = latchNode.ThenEdge.DfsPostOrder; - latchNode.IsLoopNode = true; - } - } - else { - // latch = 1-way - if (headerNode.IsTwoWay) { - headerNode.LoopType = LoopType.While; - Node node = latchNode; - while (true) { - if (node.DfsPostOrder == thenDfs) { - headerNode.LoopFollow = elseDfs; - break; - } - else if (node.DfsPostOrder == elseDfs) { - headerNode.LoopFollow = thenDfs; - break; - } - // Check if couldn't find it, then it is a strangely formed - // loop, so it is safer to consider it an endless loop - if (node.DfsPostOrder <= headerNode.DfsPostOrder) { - headerNode.LoopType = LoopType.Endless; - FindEndlessFollow(loopNodes, headerNode); - break; - } - - node = this.Cfg.DepthFirstPostOrder[node.ImmediateDominator]; - } - - if (node.DfsPostOrder > headerNode.DfsPostOrder) { - this.Cfg.DepthFirstPostOrder[headerNode.LoopFollow].LoopHead = Node.NoNode; - } - headerNode.IsLoopNode = true; - } - else { - headerNode.LoopType = LoopType.Endless; - FindEndlessFollow(loopNodes, headerNode); - } - } - } - - private void FindEndlessFollow(List loopNodes, BasicBlock head) { - head.LoopFollow = int.MaxValue; - - foreach (var i in loopNodes) { - var node = this.Cfg.DepthFirstPostOrder[i]; - if (node.IsInvalid) - continue; - - foreach (var succ in node.Successors) { - if (!loopNodes.Contains(succ.DfsPostOrder) && - (succ.DfsPostOrder < head.LoopFollow)) { - head.LoopFollow = succ.DfsPostOrder; - } - } - } - } - - private void StructureIfs() { - var bbCount = this.Cfg.DepthFirstPostOrder.Length; - var unresolved = new List(); - - // Linear scan of nodes in reverse dfsLast order - for (int cur = bbCount - 1; cur >= 0; cur--) { - BasicBlock curNode = (BasicBlock)this.Cfg.DepthFirstPostOrder[cur]; - if (curNode.IsInvalid) - continue; - - if (curNode.IsTwoWay && !curNode.IsLoopNode) { - int followInEdges = 0; - int follow = 0; - - // Find all nodes that have this node as immediate dominator - for (int desc = cur + 1; desc < bbCount; desc++) { - Node node = this.Cfg.DepthFirstPostOrder[desc]; - if (node.IsInvalid) - continue; - - if (node.ImmediateDominator == cur) { - int delta = node.Predecessors.Count - node.BackEdgeCount; - if (delta >= followInEdges) { - follow = desc; - followInEdges = delta; - } - } - } - - // Determine follow according to number of descendants - // immediately dominated by this node - if ((follow != 0) && (followInEdges > 1)) { - curNode.IfFollow = follow; - if (unresolved.Any()) { - FlagNodes(unresolved, follow); - } - } - else { - unresolved.Add(curNode); - } - } - } - } - - private void FlagNodes(List list, int follow) { - while (list.Any()) { - var node = list.Dequeue(); - node.IfFollow = follow; - } - } - } -}