Package Rakudo.Runtime.MultiDispatch

Source Code of Rakudo.Runtime.MultiDispatch.MultiDispatcher

package Rakudo.Runtime.MultiDispatch;

import java.lang.Math;
import java.util.ArrayList;
import java.util.Collections;
import Rakudo.Metamodel.RakudoObject;
import Rakudo.Metamodel.Representations.P6capture;
import Rakudo.Metamodel.Representations.RakudoCodeRef;
import Rakudo.Runtime.DefinednessConstraint;

/// <summary>
/// Very first cut implementation of a multi-dispatcher. Doesn't yet
/// know about subtyping beyond no type being the top type. Yes, this
/// will likely get replaced (or extensively re-done) at some point.
/// </summary>
public class MultiDispatcher
{
    /// <summary>
    /// Represents a node in the multi-dispatch DAG used to do the topological
    /// sort.
    /// <returns></returns>
    private class CandidateGraphNode
    {
        public RakudoCodeRef.Instance Candidate;
        public CandidateGraphNode[] Edges;
        public int EdgesIn;
        public int EdgesOut;
    }

    /// <summary>
    /// Indicates an edge should be removed in the next iteration.
    /// </summary>
    private final int EDGE_REMOVAL_TODO = -1; // C# has const

    /// <summary>
    /// Indicates that an edge was already removed.
    /// </summary>
    private final int EDGE_REMOVED = -2; // C# has const

    /// <summary>
    /// Finds the best candidate, if one exists, and returns it.
    /// </summary>
    /// <param name="Candidates"></param>
    /// <param name="Capture"></param>
    /// <returns></returns>
    public static RakudoObject FindBestCandidate(RakudoCodeRef.Instance dispatchRoutine, RakudoObject capture)
    {
        // Extract the native capture.
        // XXX Handle non-native captures too.
        P6capture.Instance nativeCapture = (P6capture.Instance)capture;

        // First, try the dispatch cache.
        if (dispatchRoutine.MultiDispatchCache != null && nativeCapture.Nameds == null)
        {
            RakudoObject cacheResult = dispatchRoutine.MultiDispatchCache.Lookup(nativeCapture.Positionals);
            if (cacheResult != null)
                return cacheResult;
        }

        // Sort the candidates.
        // XXX Cache this in the future.
        ArrayList<RakudoObject> sortedCandidates = candidateSort(dispatchRoutine.Dispatchees);

        // Now go through the sorted candidates and find the first one that
        // matches.
        ArrayList<RakudoCodeRef.Instance> possiblesList = new ArrayList<RakudoCodeRef.Instance>();
        for (RakudoObject candidateObject : sortedCandidates)
        {
            // TODO: remove yukky type cast
            RakudoCodeRef.Instance candidate = (RakudoCodeRef.Instance)candidateObject;
            // If we hit a null, we're at the end of a group.
            if (candidate == null)
            {
                if (possiblesList.size() == 1)
                {
                    // We have an unambiguous first candidate. Cache if possible and
                    // return it.
                    if (nativeCapture.Nameds == null)
                    {
                        if (dispatchRoutine.MultiDispatchCache == null)
                            dispatchRoutine.MultiDispatchCache = new DispatchCache();
                        dispatchRoutine.MultiDispatchCache.Add(nativeCapture.Positionals, possiblesList.get(0));
                    }
                    return possiblesList.get(0);
                }
                else if (possiblesList.size() > 1)
                {
                    // Here is where you'd handle constraints.
                    throw new UnsupportedOperationException("Ambiguous dispatch: more than one candidate matches");
                }
                else
                {
                    continue;
                }
            }
           
            /* Check if it's admissible by arity. */
            int numArgs = nativeCapture.Positionals.length;
            if (numArgs < candidate.Sig.NumRequiredPositionals ||
                numArgs > candidate.Sig.NumPositionals)
                continue;

            /* Check if it's admissible by type. */
            int typeCheckCount = Math.min(numArgs, candidate.Sig.NumPositionals);
            boolean typeMismatch = false;
            for (int i = 0; i < typeCheckCount; i++) {
                RakudoObject arg = nativeCapture.Positionals[i];
                RakudoObject type = candidate.Sig.Parameters[i].Type;
                if (arg.getSTable().WHAT != type && type != null)
                {
                    typeMismatch = true;
                    break;
                }
                DefinednessConstraint definedness = candidate.Sig.Parameters[i].Definedness;
                if (definedness != DefinednessConstraint.None)
                {
                    boolean argDefined = arg.getSTable().REPR.defined(null, arg);
                    if (definedness == DefinednessConstraint.DefinedOnly && !argDefined ||
                        definedness == DefinednessConstraint.UndefinedOnly && argDefined)
                    {
                        typeMismatch = true;
                        break;
                    }
                }
            }
            if (typeMismatch)
                continue;

            /* If we get here, it's an admissible candidate; add to list. */
            possiblesList.add(candidate);
        }

        // If we get here, no candidates matched.
        throw new UnsupportedOperationException("No candidates found to dispatch to");
    }

    /// <summary>
    /// Sorts the candidates.
    /// </summary>
    /// <param name="Unsorted"></param>
    /// <returns></returns>
    private static ArrayList<RakudoObject> candidateSort(RakudoObject[] unsorted)
    {
        ArrayList<RakudoObject> sorted = new ArrayList<RakudoObject>();
        for (RakudoObject obj : unsorted) {
            sorted.add(obj);
        }
        sorted.add(null); // XXX does this actually sort?
        return sorted;
    }

    /// <summary>
    /// Checks if one signature is narrower than another.
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    /// <returns></returns>
    private static int IsNarrower(RakudoCodeRef.Instance a, RakudoCodeRef.Instance b)
    {
        int Narrower = 0;
        int Tied = 0;
        int i, TypesToCheck;

        /* Work out how many parameters to compare, factoring in slurpiness
         * and optionals. */
        if (a.Sig.NumPositionals == b.Sig.NumPositionals)
            TypesToCheck = a.Sig.NumPositionals;
        else if (a.Sig.NumRequiredPositionals == b.Sig.NumRequiredPositionals)
            TypesToCheck = Math.min(a.Sig.NumPositionals, b.Sig.NumPositionals);
        else
            return 0;

        /* Analyse each parameter in the two candidates. */
        for (i = 0; i < TypesToCheck; i++) {
            RakudoObject TypeObjA = a.Sig.Parameters[i].Type;
            RakudoObject TypeObjB = b.Sig.Parameters[i].Type;
            if (TypeObjA == TypeObjB)
            {
                /* In a full Perl 6 multi dispatcher, you'd consider
                 * constraints here. */
                Tied++;
            }
            else
            {
                if (IsNarrowerType(TypeObjA, TypeObjB))
                    Narrower++;
                else if (!IsNarrowerType(TypeObjB, TypeObjA))
                    Tied++;
            }
        }

        /* If one is narrower than the other from current analysis, we're done. */
        if (Narrower >= 1 && Narrower + Tied == TypesToCheck)
            return 1;

        /* If they aren't tied, we're also done. */
        else if (Tied != TypesToCheck)
            return 0;

        /* Otherwise, we see if one has a slurpy and the other not. A lack of
        * slurpiness makes the candidate narrower. Otherwise, they're tied. */
        return !a.Sig.HasSlurpyPositional() && b.Sig.HasSlurpyPositional() ? 1 : 0;
    }

    /// <summary>
    /// Compares two types to see if the first is narrower than the second.
    /// XXX This is not complete yet, just very basic check.
    /// </summary>
    /// <returns></returns>
    public static boolean IsNarrowerType(RakudoObject A, RakudoObject B)
    {
        // For now, differentiate any (represented by null) and a type.
        if (B != null && A == null)
            return false;
        else
            return true;
    }
}
TOP

Related Classes of Rakudo.Runtime.MultiDispatch.MultiDispatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.