public void AddPolicyEdge(object state, DfaNode node) { if (PolicyEdges == null) { PolicyEdges = new Dictionary <object, DfaNode>(); } PolicyEdges.Add(state, node); }
public void AddLiteral(string literal, DfaNode node) { if (Literals == null) { Literals = new Dictionary <string, DfaNode>(StringComparer.OrdinalIgnoreCase); } Literals.Add(literal, node); }
public DfaNode BuildDfaTree() { // We build the tree by doing a BFS over the list of entries. This is important // because a 'parameter' node can also traverse the same paths that literal nodes // traverse. This means that we need to order the entries first, or else we will // miss possible edges in the DFA. _endpoints.Sort(_comparer); // Since we're doing a BFS we will process each 'level' of the tree in stages // this list will hold the set of items we need to process at the current // stage. var work = new List <(MatcherEndpoint endpoint, List <DfaNode> parents)>(); var root = new DfaNode() { Depth = 0, Label = "/" }; // To prepare for this we need to compute the max depth, as well as // a seed list of items to process (entry, root). var maxDepth = 0; for (var i = 0; i < _endpoints.Count; i++) { var endpoint = _endpoints[i]; maxDepth = Math.Max(maxDepth, endpoint.RoutePattern.PathSegments.Count); work.Add((endpoint, new List <DfaNode>() { root, })); } // Now we process the entries a level at a time. for (var depth = 0; depth <= maxDepth; depth++) { // As we process items, collect the next set of items. var nextWork = new List <(MatcherEndpoint endpoint, List <DfaNode> parents)>(); for (var i = 0; i < work.Count; i++) { var(endpoint, parents) = work[i]; if (!HasAdditionalRequiredSegments(endpoint, depth)) { for (var j = 0; j < parents.Count; j++) { var parent = parents[j]; parent.Matches.Add(endpoint); } } // Find the parents of this edge at the current depth var nextParents = new List <DfaNode>(); var segment = GetCurrentSegment(endpoint, depth); if (segment == null) { continue; } for (var j = 0; j < parents.Count; j++) { var parent = parents[j]; var part = segment.Parts[0]; if (segment.IsSimple && part is RoutePatternLiteralPart literalPart) { var literal = literalPart.Content; if (!parent.Literals.TryGetValue(literal, out var next)) { next = new DfaNode() { Depth = parent.Depth + 1, Label = parent.Label + literal + "/", }; parent.Literals.Add(literal, next); } nextParents.Add(next); } else if (segment.IsSimple && part is RoutePatternParameterPart parameterPart && parameterPart.IsCatchAll) { // A catch all should traverse all literal nodes as well as parameter nodes // we don't need to create the parameter node here because of ordering // all catchalls will be processed after all parameters. nextParents.AddRange(parent.Literals.Values); if (parent.Parameters != null) { nextParents.Add(parent.Parameters); } // We also create a 'catchall' here. We don't do further traversals // on the catchall node because only catchalls can end up here. The // catchall node allows us to capture an unlimited amount of segments // and also to match a zero-length segment, which a parameter node // doesn't allow. if (parent.CatchAll == null) { parent.CatchAll = new DfaNode() { Depth = parent.Depth + 1, Label = parent.Label + "{*...}/", }; // The catchall node just loops. parent.CatchAll.Parameters = parent.CatchAll; parent.CatchAll.CatchAll = parent.CatchAll; } parent.CatchAll.Matches.Add(endpoint); }