Example #1
0
            public virtual FuncitonFunction Parse(Dictionary<string, unparsedFunctionDeclaration> unparsedFunctionsByName, Dictionary<node, unparsedFunctionDeclaration> unparsedFunctionsByNode, Dictionary<unparsedDeclaration, FuncitonFunction> parsedFunctions)
            {
                var processedEdges = new HashSet<edge>();

                Action<edge> isCorrect = e => { processedEdges.Add(e); };
                Action<edge> isFlipped = e =>
                {
                    if (processedEdges.Contains(e))
                        throw new ParseErrorException(
                            new ParseError("Program is ambiguous: cannot determine the direction of this edge.", e.StartX, e.StartY, _source.SourceFile),
                            new ParseError("... edge ends here.", e.EndX, e.EndY, _source.SourceFile));
                    e.Flip();
                    processedEdges.Add(e);
                };

                // Deduce all the inputs and outputs on every node
                var q = new Queue<node>(Nodes);
                var enqueued = 0;
                while (q.Count > 0)
                {
                    var node = q.Dequeue();
                    var edges = new[] { direction.Up, direction.Right, direction.Down, direction.Left }
                        .Select(dir => Edges.SingleOrDefault(e => (e.StartNode == node && e.DirectionFromStartNode == dir) || (e.EndNode == node && e.DirectionFromEndNode == dir))).ToArray();
                    var known = edges.Select(e => e != null && processedEdges.Contains(e)).ToArray();
                    if (!node.Deduce(edges, known, unparsedFunctionsByName, unparsedFunctionsByNode, isCorrect, isFlipped, _source))
                    {
                        q.Enqueue(node);
                        enqueued++;
                        if (enqueued == q.Count)
                        {
                            var funcName = this is unparsedFunctionDeclaration ? "function “{0}”".Fmt(((unparsedFunctionDeclaration) this).DeclarationName) : "the main program";
                            throw new ParseErrorException(new ParseError("Program is ambiguous: cannot determine the direction of all the edges in {0}.".Fmt(funcName), null, null, _source.SourceFile));
                        }
                    }
                    else
                        enqueued = 0;
                }
                Helpers.Assert(Nodes.All(n => n.Edges != null && n.Connectors != null));

                var outputs = new FuncitonFunction.Node[4];
                parsedFunctions[this] = _function = createFuncitonFunction(outputs);

                _unparsedFunctionsByName = unparsedFunctionsByName;
                _unparsedFunctionsByNode = unparsedFunctionsByNode;
                _parsedFunctions = parsedFunctions;

                foreach (var node in Nodes.Where(n => n.Type == nodeType.End))
                {
                    Helpers.Assert(node.Edges[0] != null);
                    Helpers.Assert(node.Edges[1] == null && node.Edges[2] == null && node.Edges[3] == null);
                    outputs[(int) node.Edges[0].DirectionFromEndNode] = walk(node.Edges[0], edge.EmptyArray, node.Edges[0]).Item1;
                }
                return _function;
            }
Example #2
0
            private Tuple<FuncitonFunction.Node, edge[]> walk(edge edge, edge[] allowedDependencies, edge latestOutput)
            {
                Tuple<FuncitonFunction.Node, edge[]> tryNode;
                if (_edgesAlready.TryGetValue(edge, out tryNode))
                {
                    if (tryNode == null)
                        throw new ParseErrorException(new ParseError("The {0} has a cycle in it. It can never evaluate because it would always be an infinite loop.".Fmt(_function.Name == "" ? "main program" : "function " + _function.Name), edge.EndX, edge.EndY, _source.SourceFile));
                    var disallowedDependency = tryNode.Item2.FirstOrDefault(d => !allowedDependencies.Contains(d));
                    if (disallowedDependency != null)
                        throwDisallowedDependency(latestOutput, disallowedDependency);
                    return tryNode;
                }
                _edgesAlready[edge] = null;

                var node = edge.StartNode;
                var outputPosition = Enumerable.Range(0, 4).First(i => node.Edges[i] == edge && node.Connectors[i] == connectorType.Output);

                switch (node.Type)
                {
                    case nodeType.TJunction:
                        if (node.Connectors[0] == connectorType.Output)
                        {
                            // NAND
                            Helpers.Assert(node.Connectors[1] == connectorType.Input);
                            Helpers.Assert(node.Connectors[3] == connectorType.Input);
                            var left = walk(node.Edges[3], allowedDependencies, latestOutput);
                            var right = walk(node.Edges[1], allowedDependencies, latestOutput);
                            return _edgesAlready[edge] = new Tuple<FuncitonFunction.Node, edge[]>(
                                new FuncitonFunction.NandNode(_function, left.Item1, right.Item1),
                                left.Item2.ArrayUnion(right.Item2)
                            );
                        }
                        else
                        {
                            // splitter
                            Helpers.Assert(node.Connectors[0] == connectorType.Input);
                            Helpers.Assert(node.Connectors[1] == connectorType.Output);
                            Helpers.Assert(node.Connectors[3] == connectorType.Output);
                            if (node.Edges[0] == edge)
                                throw new ParseErrorException(new ParseError("This splitter is connected to itself. Such a construct is not allowed as it would always cause an infinite loop.", node.X, node.Y, _source.SourceFile));
                            return _edgesAlready[edge] = walk(node.Edges[0], allowedDependencies, latestOutput);
                        }

                    case nodeType.CrossJunction:
                        {
                            Helpers.Assert(node.Connectors[0] == connectorType.Input);
                            Helpers.Assert(node.Connectors[1] == connectorType.Output);
                            Helpers.Assert(node.Connectors[2] == connectorType.Output);
                            Helpers.Assert(node.Connectors[3] == connectorType.Input);
                            Helpers.Assert(node.Edges[1] == edge || node.Edges[2] == edge);

                            var left = walk(node.Edges[0], allowedDependencies, latestOutput);
                            var right = walk(node.Edges[3], allowedDependencies, latestOutput);
                            var newNode = node.Edges[1] == edge
                                ? (FuncitonFunction.Node) new FuncitonFunction.LessThanNode(_function, left.Item1, right.Item1)
                                : (FuncitonFunction.Node) new FuncitonFunction.ShiftLeftNode(_function, left.Item1, right.Item1);
                            return _edgesAlready[edge] = Tuple.Create(newNode, left.Item2.ArrayUnion(right.Item2));
                        }

                    case nodeType.Declaration:
                        return _edgesAlready[edge] = new Tuple<FuncitonFunction.Node, edge[]>(new FuncitonFunction.InputNode(_function, (int) edge.DirectionFromStartNode), edge.EmptyArray);

                    case nodeType.Call:
                        unparsedFunctionDeclaration decl;
                        if (!_unparsedFunctionsByNode.TryGetValue(node, out decl) && !_unparsedFunctionsByName.TryGetValue(node.GetContent(_source), out decl))
                            throw new ParseErrorException(new ParseError("Call to undefined function “{0}”.".Fmt(node.GetContent(_source)), node.X, node.Y, _source.SourceFile));

                        FuncitonFunction func;
                        if (!_parsedFunctions.TryGetValue(decl, out func))
                            func = decl.Parse(_unparsedFunctionsByName, _unparsedFunctionsByNode, _parsedFunctions);

                        // Try to optimise away no-op functions
                        int? inputPosition = func.GetInputForOutputIfNop(outputPosition);
                        Helpers.Assert(inputPosition == null || node.Connectors[inputPosition.Value] == connectorType.Input);
                        if (inputPosition != null)
                            return _edgesAlready[edge] = walk(node.Edges[inputPosition.Value], allowedDependencies, latestOutput);

                        if (!_callsAlready.ContainsKey(node))
                        {
                            var inputs = new FuncitonFunction.Node[4];
                            var dependencies = edge.EmptyArray;
                            for (int i = 0; i < 4; i++)
                            {
                                if (node.Connectors[i] != connectorType.Input)
                                    continue;
                                var result = walk(node.Edges[i], allowedDependencies, latestOutput);
                                inputs[i] = result.Item1;
                                dependencies = dependencies.ArrayUnion(result.Item2);
                            }
                            _callsAlready[node] = Tuple.Create(new FuncitonFunction.Call(func, inputs), dependencies);
                        }
                        return _edgesAlready[edge] = new Tuple<FuncitonFunction.Node, edge[]>(
                            new FuncitonFunction.CallOutputNode(_function, outputPosition, _callsAlready[node].Item1),
                            _callsAlready[node].Item2
                        );

                    case nodeType.Literal:
                        var content = Regex.Replace(node.GetContent(_source), @"\s*\n\s*", "").Trim().Replace('−', '-');
                        FuncitonFunction.Node newLiteralNode;
                        if (content.Length == 0)
                            newLiteralNode = new FuncitonFunction.StdInNode(_function);
                        else
                        {
                            BigInteger literal;
                            if (!BigInteger.TryParse(content, out literal))
                                throw new ParseErrorException(new ParseError("Literal does not represent a valid integer.", node.X, node.Y, _source.SourceFile));
                            newLiteralNode = new FuncitonFunction.LiteralNode(_function, literal);
                        }
                        return _edgesAlready[edge] = Tuple.Create(newLiteralNode, edge.EmptyArray);

                    case nodeType.LambdaInvocation:
                        if (!string.IsNullOrWhiteSpace(node.GetContent(_source)))
                            throw new ParseErrorException(new ParseError("Lambda invocation boxes must be empty.", node.X, node.Y, _source.SourceFile));

                        if (!_lambdasAlready.ContainsKey(node))
                        {
                            Helpers.Assert(node.Connectors[0] == connectorType.Input);
                            Helpers.Assert(node.Connectors[1] == connectorType.Output);
                            Helpers.Assert(node.Connectors[2] == connectorType.Output);
                            Helpers.Assert(node.Connectors[3] == connectorType.Input);
                            var lambdaGetter = walk(node.Edges[0], allowedDependencies, latestOutput);
                            var argument = walk(node.Edges[3], allowedDependencies, latestOutput);
                            _lambdasAlready[node] = new Tuple<FuncitonFunction.LambdaInvocation, edge[]>(
                                new FuncitonFunction.LambdaInvocation(argument.Item1, lambdaGetter.Item1),
                                lambdaGetter.Item2.ArrayUnion(argument.Item2));
                        }
                        return _edgesAlready[edge] = new Tuple<FuncitonFunction.Node, edge[]>(
                            new FuncitonFunction.LambdaInvocationOutputNode(_function, outputPosition, _lambdasAlready[node].Item1),
                            _lambdasAlready[node].Item2);

                    case nodeType.LambdaExpression:
                        if (!string.IsNullOrWhiteSpace(node.GetContent(_source)))
                            throw new ParseErrorException(new ParseError("Lambda expression boxes must be empty.", node.X, node.Y, _source.SourceFile));
                        Helpers.Assert(node.Connectors[0] == connectorType.Input);
                        Helpers.Assert(node.Connectors[1] == connectorType.Output);
                        Helpers.Assert(node.Connectors[2] == connectorType.Output);
                        Helpers.Assert(node.Connectors[3] == connectorType.Input);

                        switch (outputPosition)
                        {
                            case 1: // parameter
                                if (!allowedDependencies.Contains(edge))
                                    throwDisallowedDependency(latestOutput, edge);
                                if (!_lambdaParameters.ContainsKey(node))
                                    _lambdaParameters[node] = new FuncitonFunction.LambdaExpressionParameterNode(_function);
                                return _edgesAlready[edge] = new Tuple<FuncitonFunction.Node, FuncitonLanguage.edge[]>(_lambdaParameters[node], new[] { edge });

                            case 2: // lambdaGetter
                                // Need to put a skeleton instance into _edgesAlready because this node allows cycles
                                var clonedNode = new FuncitonFunction.LambdaExpressionNode(_function);
                                _edgesAlready[edge] = new Tuple<FuncitonFunction.Node, edge[]>(clonedNode, edge.EmptyArray);
                                // Walk the return values first so that they will create the lambda parameter node
                                var newAllowedDependencies = allowedDependencies.ArrayUnion(node.Edges[1]);
                                clonedNode.ReturnValue1 = walk(node.Edges[0], newAllowedDependencies, node.Edges[0]).Item1;
                                clonedNode.ReturnValue2 = walk(node.Edges[3], newAllowedDependencies, node.Edges[3]).Item1;
                                // If the lambda parameter is not in _lambdaParameters, it means we did not reach the lambda input and therefore the lambda
                                // ignores its input, so we can just pass a null node because it will never get evaluated anyway
                                clonedNode.Parameter = _lambdaParameters.Get(node, null);
                                return _edgesAlready[edge];

                            default:
                                throw new ParseErrorException(new ParseError("The parser encountered an internal error parsing a lambda expression.", node.X, node.Y, _source.SourceFile));
                        }

                    case nodeType.End:
                    case nodeType.Comment:
                    default:
                        throw new ParseErrorException(new ParseError("The parser encountered an internal error.", node.X, node.Y, _source.SourceFile));
                }
            }