예제 #1
0
            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;
            }
예제 #2
0
 protected unparsedDeclaration(List<node> nodes, List<edge> edges, sourceAsChars source)
 {
     Nodes = nodes;
     Edges = edges;
     _source = source;
 }
예제 #3
0
 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())));
 }
예제 #4
0
            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));
            }
예제 #5
0
 public unparsedProgram(List<node> nodes, List<edge> edges, sourceAsChars source)
     : base(nodes, edges, source)
 {
 }
예제 #6
0
            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));
                }
            }
예제 #7
0
        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();
        }