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; }
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)); } }