private bool deduceGiven(edge[] edges, bool[] known, Action<edge> isCorrect, Action<edge> isFlipped, int expected, connectorType[][] connectors, sourceAsChars source, string connectorsError, string orientationError) { if (edges.Count(e => e != null) != expected) throw new ParseErrorException(new ParseError(connectorsError, X, Y, source.SourceFile)); var result = new List<deduceInfo>(); foreach (var conn in connectors) { for (int rot = 0; rot < 4; rot++) { var rotatedEdges = edges.Skip(rot).Concat(edges.Take(rot)).ToArray(); var rotatedKnowns = known.Skip(rot).Concat(known.Take(rot)).ToArray(); var valid = Enumerable.Range(0, 4).All(i => (rotatedEdges[i] == null && conn[i] == connectorType.None) || (!rotatedKnowns[i] && conn[i] != connectorType.None) || (rotatedKnowns[i] && rotatedEdges[i].StartNode == this && conn[i] == connectorType.Output) || (rotatedKnowns[i] && rotatedEdges[i].EndNode == this && conn[i] == connectorType.Input)); if (valid) result.Add(new deduceInfo { Edges = rotatedEdges, Knowns = rotatedKnowns, Rotation = rot, Connectors = conn }); } } if (result.Count == 0) throw new ParseErrorException(new ParseError(orientationError, X, Y, source.SourceFile)); for (int i = 0; i < edges.Length; i++) { var edge = edges[i]; if (edge == null || known[i]) continue; var conns = result.Select(r => r.Connectors[(i + 4 - r.Rotation) % 4]).ToArray(); if (conns.Skip(1).All(c => c == conns[0])) { if (edge.StartNode == this && (int) edge.DirectionFromStartNode == i) (conns[0] == connectorType.Output ? isCorrect : isFlipped)(edge); else if (edge.EndNode == this && (int) edge.DirectionFromEndNode == i) (conns[0] == connectorType.Input ? isCorrect : isFlipped)(edge); } } Edges = result[0].Edges; Connectors = result[0].Connectors; return result.Count == 1; }
protected unparsedDeclaration(List<node> nodes, List<edge> edges, sourceAsChars source) { Nodes = nodes; Edges = edges; _source = source; }
public string GetContent(sourceAsChars source) { return _contentCache ?? (_contentCache = string.Join("\n", Enumerable.Range(Y + 1, Height - 1).Select(i => new string(source.Chars[i].Subarray(X + 1, Width - 1)).Trim()))); }
public bool Deduce(edge[] edges, bool[] known, Dictionary<string, unparsedFunctionDeclaration> unparsedDeclarationsByName, Dictionary<node, unparsedFunctionDeclaration> unparsedDeclarationsByNode, Action<edge> isCorrect, Action<edge> isFlipped, sourceAsChars source) { switch (Type) { case nodeType.Declaration: // Declarations have only outputs, and they are always in the correct orientation because they define it foreach (var e in edges) { if (e != null && e.StartNode == this) isCorrect(e); if (e != null && e.EndNode == this) isFlipped(e); } Edges = edges; Connectors = edges.Select(e => e == null ? connectorType.None : connectorType.Output).ToArray(); return true; case nodeType.Literal: // Literals have only outputs foreach (var e in edges) { if (e != null && e.StartNode == this) isCorrect(e); if (e != null && e.EndNode == this) isFlipped(e); } Edges = edges; Connectors = edges.Select(e => e == null ? connectorType.None : connectorType.Output).ToArray(); return true; case nodeType.Call: unparsedFunctionDeclaration func; if (!unparsedDeclarationsByNode.TryGetValue(this, out func) && !unparsedDeclarationsByName.TryGetValue(GetContent(source), out func)) throw new ParseErrorException(new ParseError("Call to undefined function: {0}".Fmt(GetContent(source)), X, Y, source.SourceFile)); return deduceGiven(edges, known, isCorrect, isFlipped, func.Connectors.Count(fc => fc != connectorType.None), new[] { func.Connectors }, source, "Incorrect number of connectors to call to function: {0}".Fmt(GetContent(source)), "Incorrect orientation of connectors to call to function: {0}".Fmt(GetContent(source))); case nodeType.TJunction: return deduceGiven(edges, known, isCorrect, isFlipped, 3, _tJunctionConnConf, source, "Incorrect number of connectors to T junction (this error indicates a bug in the parser; please report it).", "Incorrect orientation of connectors to T junction (this error indicates a bug in the parser; please report it)."); case nodeType.CrossJunction: return deduceGiven(edges, known, isCorrect, isFlipped, 4, _connConf, source, "Incorrect number of connectors to cross junction (this error indicates a bug in the parser; please report it).", "Incorrect orientation of connectors to cross junction (this error indicates a bug in the parser; please report it)."); case nodeType.LambdaExpression: return deduceGiven(edges, known, isCorrect, isFlipped, 4, _connConf, source, "Lambda expressions must have four connectors.", "Lambda expressions must have two adjacent inputs and two adjacent outputs."); case nodeType.LambdaInvocation: return deduceGiven(edges, known, isCorrect, isFlipped, 4, _connConf, source, "Lambda invocations must have four connectors.", "Lambda invocations must have two adjacent inputs and two adjacent outputs."); case nodeType.End: return deduceGiven(edges, known, isCorrect, isFlipped, 1, _endConnConf, source, "Incorrect number of connectors to end node (this error indicates a bug in the parser; please report it).", "Incorrect orientation of connectors to end node (this error indicates a bug in the parser; please report it)."); } throw new ParseErrorException(new ParseError("The parser encountered an internal error: unrecognised node type: {0}".Fmt(Type), X, Y, source.SourceFile)); }
public unparsedProgram(List<node> nodes, List<edge> edges, sourceAsChars source) : base(nodes, edges, source) { }
public unparsedFunctionDeclaration(List<node> nodes, List<edge> edges, sourceAsChars source) : base(nodes, edges, source) { var decls = Nodes.Where(n => n.Type == nodeType.Declaration).ToList(); if (decls.Count > 1) throw new ParseErrorException( new ParseError("Cannot have more than one declaration box connected with each other.", decls[0].X, decls[0].Y, _source.SourceFile), new ParseError("... other declaration box is here.", decls[1].X, decls[1].Y, _source.SourceFile)); DeclarationNode = decls[0]; if (DeclarationNode.Height != 2) throw new ParseErrorException(new ParseError("Declaration box must have exactly one line of content.", DeclarationNode.X, DeclarationNode.Y, _source.SourceFile)); foreach (var callbox in nodes.Where(n => n.Type == nodeType.Call)) if (callbox.Height != 2) throw new ParseErrorException(new ParseError("Call box must have exactly one line of content.", callbox.X, callbox.Y, _source.SourceFile)); DeclarationName = new string(source.Chars[DeclarationNode.Y + 1].Subarray(DeclarationNode.X + 1, DeclarationNode.Width - 1)).Trim(); if (DeclarationName.Length < 1) throw new ParseErrorException(new ParseError("Function name missing.", DeclarationNode.X, DeclarationNode.Y, _source.SourceFile)); DeclarationIsPrivate = false; // Find the private marker var privateMarkerPosition = 0; var left = source.RightLine(DeclarationNode.X, DeclarationNode.Y + 1); if (left == lineType.Double) throw new ParseErrorException(new ParseError("Unrecognised marker.", DeclarationNode.X, DeclarationNode.Y + 1, _source.SourceFile)); else if (left == lineType.Single) { var shape = source.GetLineShape(DeclarationNode.X, DeclarationNode.Y + 1, direction.Right, DeclarationNode.X, DeclarationNode.Y, DeclarationNode.X + DeclarationNode.Width, DeclarationNode.Y + DeclarationNode.Height); if (shape == "→↑" || shape == "→↓") { DeclarationIsPrivate = true; DeclarationName = new string(source.Chars[DeclarationNode.Y + 1].Subarray(DeclarationNode.X + 2, DeclarationNode.Width - 2)).Trim(); privateMarkerPosition = shape == "→↑" ? 1 : 3; } else throw new ParseErrorException(new ParseError("Unrecognised marker.", DeclarationNode.X, DeclarationNode.Y + 1, _source.SourceFile)); } var right = source.LeftLine(DeclarationNode.X + DeclarationNode.Width, DeclarationNode.Y + 1); if (right == lineType.Double) throw new ParseErrorException(new ParseError("Unrecognised marker.", DeclarationNode.X + DeclarationNode.Width, DeclarationNode.Y + 1, _source.SourceFile)); else if (right == lineType.Single) { var shape = source.GetLineShape(DeclarationNode.X, DeclarationNode.Y + 1, direction.Left, DeclarationNode.X, DeclarationNode.Y, DeclarationNode.X + DeclarationNode.Width, DeclarationNode.Y + DeclarationNode.Height); if (shape == "←↑" || shape == "←↓") { if (DeclarationIsPrivate) throw new ParseErrorException(new ParseError("Duplicate private marker.", DeclarationNode.X + DeclarationNode.Width, DeclarationNode.Y + 1, _source.SourceFile)); DeclarationIsPrivate = true; DeclarationName = new string(source.Chars[DeclarationNode.Y + 1].Subarray(DeclarationNode.X + 1, DeclarationNode.Width - 2)).Trim(); privateMarkerPosition = shape == "←↑" ? 2 : 4; } else throw new ParseErrorException(new ParseError("Unrecognised marker.", DeclarationNode.X + DeclarationNode.Width, DeclarationNode.Y + 1, _source.SourceFile)); } for (int i = DeclarationNode.X + 1; i < DeclarationNode.X + DeclarationNode.Width; i++) { if ((i != DeclarationNode.X + 1 || privateMarkerPosition != 1) && (i != DeclarationNode.X + DeclarationNode.Width - 1 || privateMarkerPosition != 2)) if (source.BottomLine(i, DeclarationNode.Y) != lineType.None) throw new ParseErrorException(new ParseError("Unrecognised marker.", i, DeclarationNode.Y, _source.SourceFile)); if ((i != DeclarationNode.X + 1 || privateMarkerPosition != 3) && (i != DeclarationNode.X + DeclarationNode.Width - 1 || privateMarkerPosition != 4)) if (source.TopLine(i, DeclarationNode.Y + DeclarationNode.Height) != lineType.None) throw new ParseErrorException(new ParseError("Unrecognised marker.", i, DeclarationNode.Y + DeclarationNode.Height, _source.SourceFile)); } }
private static object compileAndAnalyse(IEnumerable<string> paths, List<string> functionNamesToAnalyse) { unparsedProgram program = null; Dictionary<string, unparsedDeclaration> functionsToAnalyse = new Dictionary<string, unparsedDeclaration>(); var declarationsByCallNode = new Dictionary<node, unparsedFunctionDeclaration>(); var declarationsByName = new Dictionary<string, unparsedFunctionDeclaration>(); foreach (var sourceFile in paths) { var sourceText = File.ReadAllText(sourceFile); // Turn into array of characters var lines = (sourceText.Replace("\r", "") + "\n\n").Split('\n'); if (lines.Length == 0) continue; var longestLine = lines.Max(l => l.Length); if (longestLine == 0) continue; var source = new sourceAsChars(lines.Select(l => l.PadRight(longestLine).ToCharArray()).ToArray(), sourceFile); // Find boxes and their outgoing edges var nodes = new List<node>(); var unfinishedEdges = new List<unfinishedEdge>(); for (int y = 0; y < source.Height; y++) { for (int x = 0; x < source.Width; x++) { // Start finding a box here if this is a top-left corner of a box if (source.TopLine(x, y) != lineType.None || source.LeftLine(x, y) != lineType.None || source.RightLine(x, y) == lineType.None || source.BottomLine(x, y) == lineType.None) continue; // Find width of box by walking along top edge var top = source.RightLine(x, y); var index = x + 1; while (index < source.Width && source.RightLine(index, y) == top) index++; if (index == source.Width || source.BottomLine(index, y) == lineType.None || source.TopLine(index, y) != lineType.None || source.RightLine(index, y) != lineType.None) continue; var width = index - x; // Find height of box by walking along left edge var left = source.BottomLine(x, y); index = y + 1; while (index < source.Height && source.BottomLine(x, index) == left) index++; if (index == source.Height || source.RightLine(x, index) == lineType.None || source.LeftLine(x, index) != lineType.None || source.BottomLine(x, index) != lineType.None) continue; var height = index - y; // Verify the bottom edge var bottom = source.RightLine(x, y + height); index = x + 1; while (index < source.Width && source.RightLine(index, y + height) == bottom) index++; if (index == source.Width || source.TopLine(index, y + height) == lineType.None || source.BottomLine(index, y + height) != lineType.None || source.RightLine(index, y + height) != lineType.None) continue; if (index - x != width) continue; // Verify the right edge var right = source.BottomLine(x + width, y); index = y + 1; while (index < source.Height && source.BottomLine(x + width, index) == right) index++; if (index == source.Height || source.LeftLine(x + width, index) == lineType.None || source.RightLine(x + width, index) != lineType.None || source.BottomLine(x + width, index) != lineType.None) continue; if (index - y != height) continue; // Determine type of box nodeType type; var edgeTypes = new[] { left, top, right, bottom }; switch (edgeTypes.Count(e => e == lineType.Double)) { case 0: // Not actually a box but a NAND square continue; case 1: type = nodeType.LambdaInvocation; break; case 2: type = edgeTypes[0] != edgeTypes[1] && edgeTypes[1] != edgeTypes[2] ? nodeType.Declaration : nodeType.Call; break; case 3: type = nodeType.LambdaExpression; break; case 4: type = nodeType.Literal; break; default: throw new ParseErrorException(new ParseError("Unrecognised box type.", x, y, sourceFile)); } // Right now, “type” is “Literal” if it is a double-lined box, but it could be a Comment too, // so don’t create the box yet. When we encounter an outgoing edge, we’ll know it’s a literal. node box = null; Func<node> getBox = () => box ?? (box = new node(x, y, width, height, type)); // Search for outgoing edges unfinishedEdge topEdge = null, rightEdge = null, bottomEdge = null, leftEdge = null; for (int i = x + 1; i < x + width; i++) { if (source.TopLine(i, y) == lineType.Double) throw new ParseErrorException(new ParseError("Box has outgoing double edge.", i, y, sourceFile)); else if (source.TopLine(i, y) == lineType.Single) { if (topEdge != null) throw new ParseErrorException(new ParseError("Box has duplicate outgoing edge along the top.", i, y, sourceFile)); topEdge = new unfinishedEdge { StartNode = getBox(), DirectionFromStartNode = direction.Up, DirectionGoingTo = direction.Up, StartX = i, StartY = y, EndX = i, EndY = y }; } if (source.BottomLine(i, y + height) == lineType.Double) throw new ParseErrorException(new ParseError("Box has outgoing double edge.", i, y + height, sourceFile)); else if (source.BottomLine(i, y + height) == lineType.Single) { if (bottomEdge != null) throw new ParseErrorException(new ParseError("Box has duplicate outgoing edge along the bottom.", i, y + height, sourceFile)); bottomEdge = new unfinishedEdge { StartNode = getBox(), DirectionFromStartNode = direction.Down, DirectionGoingTo = direction.Down, StartX = i, StartY = y + height, EndX = i, EndY = y + height }; } } for (int i = y + 1; i < y + height; i++) { if (source.LeftLine(x, i) == lineType.Double) throw new ParseErrorException(new ParseError("Box has outgoing double edge.", x, i, sourceFile)); else if (source.LeftLine(x, i) == lineType.Single) { if (leftEdge != null) throw new ParseErrorException(new ParseError("Box has duplicate outgoing edge along the left.", x, i, sourceFile)); leftEdge = new unfinishedEdge { StartNode = getBox(), DirectionFromStartNode = direction.Left, DirectionGoingTo = direction.Left, StartX = x, StartY = i, EndX = x, EndY = i }; } if (source.RightLine(x + width, i) == lineType.Double) throw new ParseErrorException(new ParseError("Box has outgoing double edge.", x + width, i, sourceFile)); else if (source.RightLine(x + width, i) == lineType.Single) { if (rightEdge != null) throw new ParseErrorException(new ParseError("Box has duplicate outgoing edge along the right.", x + width, i, sourceFile)); rightEdge = new unfinishedEdge { StartNode = getBox(), DirectionFromStartNode = direction.Right, DirectionGoingTo = direction.Right, StartX = x + width, StartY = i, EndX = x + width, EndY = i }; } } // If box is still null, then it has no outgoing edges. if (box == null) { if (type == nodeType.Literal) type = nodeType.Comment; else throw new ParseErrorException(new ParseError("Box without outgoing edges not allowed unless it has only double-lined edges (making it a comment).", x, y, sourceFile)); } // If it’s a comment, kill its contents so that it can contain boxes if it wants to. if (type == nodeType.Comment) { for (int yy = y; yy <= y + height; yy++) for (int xx = x; xx <= x + width; xx++) source.Chars[yy][xx] = ' '; } else { nodes.Add(getBox()); unfinishedEdges.AddRange(new[] { topEdge, rightEdge, bottomEdge, leftEdge }.Where(e => e != null)); } } } // Add T-junctions and cross-junctions (but not loose ends yet), and also complain about any stray characters for (int y = 0; y < source.Height; y++) { for (int x = 0; x < source.Width; x++) { if (source.Chars[y][x] == ' ') continue; // ignore boxes if (nodes.Any(b => b.X <= x && b.X + b.Width >= x && b.Y <= y && b.Y + b.Height >= y)) continue; if ((!source.AnyLine(x, y) || source.TopLine(x, y) == lineType.Double || source.LeftLine(x, y) == lineType.Double || source.BottomLine(x, y) == lineType.Double || source.RightLine(x, y) == lineType.Double)) throw new ParseErrorException(new ParseError("Stray character: " + source.Chars[y][x], x, y, sourceFile)); if (x < source.Width - 1 && source.RightLine(x, y) != lineType.None && source.LeftLine(x + 1, y) != lineType.None && source.RightLine(x, y) != source.LeftLine(x + 1, y)) throw new ParseErrorException(new ParseError("Single line cannot suddenly switch to double line.", x + 1, y, sourceFile)); if (y < source.Height - 1 && source.BottomLine(x, y) != lineType.None && source.TopLine(x, y + 1) != lineType.None && source.BottomLine(x, y) != source.TopLine(x, y + 1)) throw new ParseErrorException(new ParseError("Single line cannot suddenly switch to double line.", x, y + 1, sourceFile)); var singleLines = new[] { source.TopLine(x, y), source.RightLine(x, y), source.BottomLine(x, y), source.LeftLine(x, y) }.Select(line => line == lineType.Single).ToArray(); var count = singleLines.Count(sl => sl); if (count < 3) continue; var nodetype = count == 4 ? nodeType.CrossJunction : nodeType.TJunction; var node = new node(x, y, 0, 0, nodetype); nodes.Add(node); for (int i = 0; i < 4; i++) if (singleLines[i]) unfinishedEdges.Add(new unfinishedEdge { StartNode = node, DirectionFromStartNode = (direction) i, StartX = x, StartY = y, EndX = x, EndY = y, DirectionGoingTo = (direction) i }); } } // Parse the connections between nodes and discover all the loose ends var visited = new bool[source.Chars.Length][]; for (int i = visited.Length - 1; i >= 0; i--) visited[i] = new bool[source.Chars[0].Length]; var edges = new List<edge>(); while (unfinishedEdges.Count > 0) { var edge = unfinishedEdges[0]; int x = edge.EndX, y = edge.EndY; lineType connector; switch (edge.DirectionGoingTo) { case direction.Up: y--; connector = source.BottomLine(x, y); break; case direction.Left: x--; connector = source.RightLine(x, y); break; case direction.Down: y++; connector = source.TopLine(x, y); break; case direction.Right: x++; connector = source.LeftLine(x, y); break; default: throw new ParseErrorException(new ParseError("The parser encountered an internal error.", x, y, sourceFile)); } if (y >= 0 && y < visited.Length && x >= 0 && x < visited[y].Length) visited[y][x] = true; switch (connector) { case lineType.None: // We encountered a loose end unfinishedEdges.RemoveAt(0); var node = new node(edge.EndX, edge.EndY, 0, 0, nodeType.End); edges.Add(new edge(edge.StartNode, edge.DirectionFromStartNode, node, opposite(edge.DirectionGoingTo), edge.StartX, edge.StartY, edge.EndX, edge.EndY)); nodes.Add(node); break; case lineType.Single: // Check whether this edge connects to any other edge var otherEdge = unfinishedEdges.FirstOrDefault(ue => ue.EndX == x && ue.EndY == y && ue.DirectionGoingTo == opposite(edge.DirectionGoingTo)); if (otherEdge != null) { unfinishedEdges.RemoveAt(0); unfinishedEdges.Remove(otherEdge); edges.Add(new edge(edge.StartNode, edge.DirectionFromStartNode, otherEdge.StartNode, otherEdge.DirectionFromStartNode, edge.StartX, edge.StartY, x, y)); break; } // We can now assume this is not a junction, so just check which direction it’s going edge.DirectionGoingTo = edge.DirectionGoingTo != direction.Down && source.TopLine(x, y) == lineType.Single ? direction.Up : edge.DirectionGoingTo != direction.Up && source.BottomLine(x, y) == lineType.Single ? direction.Down : edge.DirectionGoingTo != direction.Left && source.RightLine(x, y) == lineType.Single ? direction.Right : edge.DirectionGoingTo != direction.Right && source.LeftLine(x, y) == lineType.Single ? direction.Left : Helpers.Throw<direction>(new ParseErrorException(new ParseError("The parser encountered an internal error.", x, y, sourceFile))); edge.EndX = x; edge.EndY = y; break; case lineType.Double: default: throw new ParseErrorException(new ParseError("Unexpected double line.", x, y, sourceFile)); } } // Complain about any extraneous characters anywhere for (int y = 0; y < source.Height; y++) for (int x = 0; x < source.Width; x++) if (source.Chars[y][x] != ' ' && !visited[y][x] && !nodes.Any(b => b.X <= x && b.X + b.Width >= x && b.Y <= y && b.Y + b.Height >= y)) throw new ParseErrorException(new ParseError("Stray line not connected to any program or function.", x, y, sourceFile)); // Collect everything that is connected to each declaration node List<node> collectedNodes; List<edge> collectedEdges; var declarations = new List<unparsedFunctionDeclaration>(); while (true) { var declaration = nodes.FirstOrDefault(n => n.Type == nodeType.Declaration); if (declaration == null) break; collectAllConnected(nodes, edges, declaration, out collectedNodes, out collectedEdges); var unparsedFunction = new unparsedFunctionDeclaration(collectedNodes, collectedEdges, source); declarations.Add(unparsedFunction); if (functionNamesToAnalyse != null && functionNamesToAnalyse.Contains(unparsedFunction.DeclarationName)) functionsToAnalyse[unparsedFunction.DeclarationName] = unparsedFunction; } // If there is anything left, it must be the program. There must be exactly one output var outputs = nodes.Where(n => n.Type == nodeType.End).ToList(); if (outputs.Count > 1) throw new ParseErrorException(new ParseError("Cannot have more than one program output.", outputs[1].X, outputs[1].Y, sourceFile)); else if (outputs.Count == 1) { if (program != null) throw new ParseErrorException(new ParseError("Cannot have more than one program.", outputs[0].X, outputs[0].Y, sourceFile)); collectAllConnected(nodes, edges, outputs[0], out collectedNodes, out collectedEdges); program = new unparsedProgram(collectedNodes, collectedEdges, source); } // If there is *still* anything left (other than comments), it’s an error var strayNode = nodes.FirstOrDefault(n => n.Type != nodeType.Comment); if (strayNode != null) throw new ParseErrorException(new ParseError("Stray node unconnected to any declaration or program.", strayNode.X, strayNode.Y, sourceFile)); var strayEdge = edges.FirstOrDefault(); if (strayEdge != null) throw new ParseErrorException(new ParseError("Stray edge unconnected to any declaration or program.", strayEdge.StartX, strayEdge.StartY, sourceFile)); // Check that all function names are unique var privateDeclarationsByName = new Dictionary<string, unparsedFunctionDeclaration>(); foreach (var decl in declarations) { if (declarationsByName.ContainsKey(decl.DeclarationName) || privateDeclarationsByName.ContainsKey(decl.DeclarationName)) throw new ParseErrorException(new ParseError("Duplicate function declaration: ‘{0}’.".Fmt(decl.DeclarationName), decl.DeclarationNode.X, decl.DeclarationNode.Y, sourceFile)); (decl.DeclarationIsPrivate ? privateDeclarationsByName : declarationsByName)[decl.DeclarationName] = decl; } // Associate all the call nodes that call a private function with the relevant declaration IEnumerable<unparsedDeclaration> decls = declarations; if (outputs.Count == 1) decls = decls.Concat(new unparsedDeclaration[] { program }); foreach (var decl in decls) foreach (var node in decl.Nodes.Where(n => n.Type == nodeType.Call)) { unparsedFunctionDeclaration ufd; if (privateDeclarationsByName.TryGetValue(node.GetContent(source), out ufd)) declarationsByCallNode[node] = ufd; } } if (program == null) throw new ParseErrorException(new ParseError("Source files do not contain a program (program must have an output).")); var functions = new Dictionary<unparsedDeclaration, FuncitonFunction>(); if (functionNamesToAnalyse == null) return program.Parse(declarationsByName, declarationsByCallNode, functions); var sb = new StringBuilder(); foreach (var functionName in functionNamesToAnalyse) { if (functionName == "") program.Parse(declarationsByName, declarationsByCallNode, functions).Analyse(sb); else if (!functionsToAnalyse.ContainsKey(functionName)) sb.AppendLine(string.Format("No such function: “{0}”.", functionName)); else functionsToAnalyse[functionName].Parse(declarationsByName, declarationsByCallNode, functions).Analyse(sb); sb.AppendLine(); } return sb.ToString(); }