/// <summary> /// Boxes a native string into its matching value type. /// </summary> /// <param name="Value"></param> /// <returns></returns> public static RakudoObject box_str(ThreadContext TC, string Value, RakudoObject To) { var REPR = To.STable.REPR; var Result = REPR.instance_of(TC, To); REPR.set_str(TC, Result, Value); return Result; }
/// <summary> /// Binds a value at a given positional index from a low level list /// (something that uses the P6list representation). /// </summary> /// <param name="TC"></param> /// <param name="LLList"></param> /// <param name="Index"></param> /// <returns></returns> public static RakudoObject lllist_bind_at_pos(ThreadContext TC, RakudoObject LLList, RakudoObject IndexObj, RakudoObject Value) { if (LLList is P6list.Instance) { var Storage = ((P6list.Instance)LLList).Storage; var Index = Ops.unbox_int(TC, IndexObj); if (Index < Storage.Count) { Storage[Index] = Value; } else { // XXX Need some more efficient resizable array approach... // Also this is no way thread safe. while (Index > Storage.Count) Storage.Add(null); Storage.Add(Value); } return Value; } else { throw new Exception("Cannot use lllist_bind_at_pos if representation is not P6list"); } }
/// <summary> /// Forms a capture from the provided positional and named arguments. /// </summary> /// <param name="PosArgs"></param> /// <param name="NamedArgs"></param> /// <returns></returns> public static RakudoObject FormWith(RakudoObject[] PosArgs, Dictionary<string, RakudoObject> NamedArgs) { var C = (P6capture.Instance)CaptureTypeObject.STable.REPR.instance_of(null, CaptureTypeObject); C.Positionals = PosArgs; C.Nameds = NamedArgs; return C; }
/// <summary> /// Creates an instantiation of the dispatch routine (or proto, which may /// serve as one) supplied and augments it with the provided candidates. /// It relies on being passed the instantiation of the dispatcher from the /// last outer scope that had an instantiation, and we thus take its /// candidates. This may or may not hold up in the long run; it works out /// in the Perl 6-y "you can make a new instance from any object" sense /// though, and seems more likely to get the closure semantics right than /// any of the other approaches I've considered so far. /// </summary> /// <param name="TC"></param> /// <param name="ToInstantiate"></param> /// <param name="ExtraDispatchees"></param> /// <returns></returns> public static RakudoObject create_dispatch_and_add_candidates(ThreadContext TC, RakudoObject ToInstantiate, RakudoObject ExtraDispatchees) { // Make sure we got the right things. var Source = ToInstantiate as RakudoCodeRef.Instance; var AdditionalDispatchList = ExtraDispatchees as P6list.Instance; if (Source == null || AdditionalDispatchList == null) throw new Exception("create_dispatch_and_add_candidates expects a RakudoCodeRef and a P6list"); // Clone all but SC (since it's a new object and doesn't live in any // SC yet) and dispatchees (which we want to munge). var NewDispatch = new RakudoCodeRef.Instance(Source.STable); NewDispatch.Body = Source.Body; NewDispatch.CurrentContext = Source.CurrentContext; NewDispatch.Handlers = Source.Handlers; NewDispatch.OuterBlock = Source.OuterBlock; NewDispatch.OuterForNextInvocation = Source.OuterForNextInvocation; NewDispatch.Sig = Source.Sig; NewDispatch.StaticLexPad = Source.StaticLexPad; // Take existing candidates and add new ones. NewDispatch.Dispatchees = new RakudoObject[Source.Dispatchees.Length + AdditionalDispatchList.Storage.Count]; var i = 0; for (int j = 0; j < Source.Dispatchees.Length; j++) NewDispatch.Dispatchees[i++] = Source.Dispatchees[j]; for (int j = 0; j < AdditionalDispatchList.Storage.Count; j++) NewDispatch.Dispatchees[i++] = AdditionalDispatchList.Storage[j]; return NewDispatch; }
/// <summary> /// Adds the given result to the dispatch cache for the provided /// positional arguments. /// </summary> /// <param name="Positionals"></param> /// <param name="Result"></param> public void Add(RakudoObject[] Positionals, RakudoObject Result) { // Don't cache things with excessive arity. if (Positionals.Length <= MAX_ARITY) { // Compute the type cache ID tuple. var ToAdd = PositionalsToTypeCacheIDs(Positionals); // Snapshot the previous arity cache. var Previous = ArityCaches[Positionals.Length]; // Build a new one. var New = new ArityCache(); if (Previous == null) { // First time. We go in slot 0. New.NumEntries = 1; New.Results = new RakudoObject[MAX_ENTRIES + 1]; New.TypeIDs = new long[MAX_ENTRIES * Positionals.Length]; for (int i = 0; i < ToAdd.Length; i++) New.TypeIDs[i] = ToAdd[i]; New.Results[0] = Result; } else { // Copy existing entries. New.NumEntries = Previous.NumEntries; New.TypeIDs = (long[])Previous.TypeIDs.Clone(); New.Results = (RakudoObject[])Previous.Results.Clone(); // Space for the new one? if (New.NumEntries <= MAX_ENTRIES) { // We can go on the end. int i, j; for (i = 0, j = New.NumEntries * ToAdd.Length; i < ToAdd.Length; i++, j++) New.TypeIDs[j] = ToAdd[i]; New.Results[New.NumEntries] = Result; New.NumEntries++; } else { // Pick a victim. var Evictee = new Random().Next(MAX_ENTRIES + 1); int i, j; for (i = 0, j = Evictee * ToAdd.Length; i < ToAdd.Length; i++, j++) New.TypeIDs[j] = ToAdd[i]; New.Results[Evictee] = Result; } } // Pop it in place, if nothing beat us to it. Otherwise, // we let whatever slipped in first win. (We may find it is // also beneficial to loop here and try to add this entry // again, but may be too much churn, and if it a given combination // is called a lot, it'll make it in at some point.) Interlocked.CompareExchange<ArityCache>(ref ArityCaches[ToAdd.Length], New, Previous); } }
/// <summary> /// Checks if a routine is considered a dispatcher (that is, if it has a /// candidate list). /// </summary> /// <param name="TC"></param> /// <param name="Check"></param> /// <returns></returns> public static RakudoObject is_dispatcher(ThreadContext TC, RakudoObject Check) { var Checkee = Check as RakudoCodeRef.Instance; if (Checkee != null && Checkee.Dispatchees != null) return Ops.box_int(TC, 1, TC.DefaultBoolBoxType); else return Ops.box_int(TC, 0, TC.DefaultBoolBoxType); }
/// <summary> /// Initializes the context. /// </summary> /// <param name="StaticCodeObject"></param> /// <param name="Caller"></param> public Context(RakudoObject StaticCodeObject_Uncast, Context Caller, RakudoObject Capture) { // Set up static code object and caller pointers. var StaticCodeObject = (RakudoCodeRef.Instance)StaticCodeObject_Uncast; this.StaticCodeObject = StaticCodeObject; this.Caller = Caller; this.Capture = Capture; // Static sub object should have this as the current // context. StaticCodeObject.CurrentContext = this; // Lex pad should be an "instantiation" of the static one. // Instantiating a lexpad creates a new dynamic instance of it // from a static one, copying over the slot storage entries // from the static one but sharing the slot mapping. this.LexPad.SlotMapping = StaticCodeObject.StaticLexPad.SlotMapping; this.LexPad.Storage = (RakudoObject[])StaticCodeObject.StaticLexPad.Storage.Clone(); // Set outer context. if (StaticCodeObject.OuterForNextInvocation != null) { this.Outer = StaticCodeObject.OuterForNextInvocation; } else if (StaticCodeObject.OuterBlock.CurrentContext != null) { this.Outer = StaticCodeObject.OuterBlock.CurrentContext; } else { // Auto-close. In this we go setting up fake contexts // that use the static lexpad until we find a real one. var CurContext = this; var OuterBlock = StaticCodeObject.OuterBlock; while (OuterBlock != null) { // If we found a block with a context, we're done. if (OuterBlock.CurrentContext != null) { CurContext.Outer = OuterBlock.CurrentContext; break; } // Build the fake context. var OuterContext = new Context(); OuterContext.StaticCodeObject = OuterBlock; OuterContext.LexPad = OuterBlock.StaticLexPad; // Link it. CurContext.Outer = OuterContext; // Step back one level. CurContext = OuterContext; OuterBlock = OuterBlock.OuterBlock; } } }
/// <summary> /// Gets a value at a given positional index from a low level list /// (something that uses the P6list representation). /// </summary> /// <param name="TC"></param> /// <param name="LLList"></param> /// <param name="Index"></param> /// <returns></returns> public static RakudoObject lllist_get_at_pos(ThreadContext TC, RakudoObject LLList, RakudoObject Index) { if (LLList is P6list.Instance) { return ((P6list.Instance)LLList).Storage[Ops.unbox_int(TC, Index)]; } else { throw new Exception("Cannot use lllist_get_at_pos if representation is not P6list"); } }
/// <summary> /// Gets the number of elements in a low level list (something that /// uses the P6list representation). /// </summary> /// <param name="TC"></param> /// <param name="LLList"></param> /// <returns></returns> public static RakudoObject lllist_elems(ThreadContext TC, RakudoObject LLList) { if (LLList is P6list.Instance) { return Ops.box_int(TC, ((P6list.Instance)LLList).Storage.Count, TC.DefaultIntBoxType); } else { throw new Exception("Cannot use lllist_elems if representation is not P6list"); } }
/// <summary> /// Loads a module (that is, some pre-compiled compilation unit that /// was compiled using NQP). Expects the path minus an extension /// (that is, the .dll will be added). Returns what the body of the /// compilation unit evaluated to. /// </summary> /// <param name="TC"></param> /// <param name="Path"></param> /// <returns></returns> public static RakudoObject load_module(ThreadContext TC, RakudoObject Path) { // Load the assembly and grab the first type in it. var Assembly = AppDomain.CurrentDomain.Load(Ops.unbox_str(TC, Path)); var Class = Assembly.GetTypes()[0]; // Call the Load method, passing along the current thread context // and the setting to use with it. What's returned is what the main // body of the compilation unit evaluates to. var Method = Class.GetMethod("Load", BindingFlags.NonPublic | BindingFlags.Static); return (RakudoObject)Method.Invoke(null, new object[] { TC, TC.Domain.Setting }); }
/// <summary> /// Loads the setting with the given name. /// </summary> /// <param name="Name"></param> /// <param name="KnowHOW"></param> /// <returns></returns> public static Context LoadSetting(string Name, RakudoObject KnowHOW, RakudoObject KnowHOWAttribute) { // Load the assembly. var SettingAssembly = AppDomain.CurrentDomain.Load(Name); // Find the setting type and its LoadSetting method. var Class = SettingAssembly.GetType("NQPSetting"); var Method = Class.GetMethod("LoadSetting", BindingFlags.NonPublic | BindingFlags.Static); // Run it to get the context we want. var SettingContext = (Context)Method.Invoke(null, new object[] { }); // Fudge a few more things in. // XXX Should be able to toss all of these but KnowHOW. SettingContext.LexPad.Extend(new string[] { "KnowHOW", "KnowHOWAttribute", "print", "say", "capture" }); SettingContext.LexPad.SetByName("KnowHOW", KnowHOW); SettingContext.LexPad.SetByName("KnowHOWAttribute", KnowHOWAttribute); SettingContext.LexPad.SetByName("print", CodeObjectUtility.WrapNativeMethod((TC, self, C) => { for (int i = 0; i < CaptureHelper.NumPositionals(C); i++) { var Value = CaptureHelper.GetPositional(C, i); var StrMeth = self.STable.FindMethod(TC, Value, "Str", 0); var StrVal = StrMeth.STable.Invoke(TC, StrMeth, CaptureHelper.FormWith( new RakudoObject[] { Value })); Console.Write(Ops.unbox_str(null, StrVal)); } return CaptureHelper.Nil(); })); SettingContext.LexPad.SetByName("say", CodeObjectUtility.WrapNativeMethod((TC, self, C) => { for (int i = 0; i < CaptureHelper.NumPositionals(C); i++) { var Value = CaptureHelper.GetPositional(C, i); var StrMeth = self.STable.FindMethod(TC, Value, "Str", 0); var StrVal = StrMeth.STable.Invoke(TC, StrMeth, CaptureHelper.FormWith( new RakudoObject[] { Value })); Console.Write(Ops.unbox_str(null, StrVal)); } Console.WriteLine(); return CaptureHelper.Nil(); })); SettingContext.LexPad.SetByName("capture", REPRRegistry.get_REPR_by_name("P6capture").type_object_for(null, null)); return SettingContext; }
/// <summary> /// Binds the given value to a lexical variable of the given name. /// </summary> /// <param name="TC"></param> /// <param name="name"></param> /// <returns></returns> public static RakudoObject bind_lex(ThreadContext TC, string Name, RakudoObject Value) { var CurContext = TC.CurrentContext; while (CurContext != null) { int Index; if (CurContext.LexPad.SlotMapping.TryGetValue(Name, out Index)) { CurContext.LexPad.Storage[Index] = Value; return Value; } CurContext = CurContext.Outer; } throw new InvalidOperationException("No variable " + Name + " found in the lexical scope"); }
/// <summary> /// Gets a value at a given key from a low level mapping (something that /// uses the P6mapping representation). /// </summary> /// <param name="TC"></param> /// <param name="LLMapping"></param> /// <param name="Key"></param> /// <returns></returns> public static RakudoObject llmapping_get_at_key(ThreadContext TC, RakudoObject LLMapping, RakudoObject Key) { if (LLMapping is P6mapping.Instance) { var Storage = ((P6mapping.Instance)LLMapping).Storage; var StrKey = Ops.unbox_str(TC, Key); if (Storage.ContainsKey(StrKey)) return Storage[StrKey]; else return null; } else { throw new Exception("Cannot use llmapping_get_at_key if representation is not P6mapping"); } }
/// <summary> /// Dies from an unhandled exception. /// </summary> /// <param name="Exception"></param> public static void DieFromUnhandledException(ThreadContext TC, RakudoObject Exception) { // Try to stringify the exception object. try { var StrMeth = Exception.STable.FindMethod(TC, Exception, "Str", Hints.NO_HINT); var Stringified = StrMeth.STable.Invoke(TC, StrMeth, CaptureHelper.FormWith(new RakudoObject[] { Exception })); Console.WriteLine(Ops.unbox_str(TC, Stringified)); } catch { Console.Error.WriteLine("Died from an exception, and died trying to stringify it too."); } // Exit with an error code. Environment.Exit(1); }
/// <summary> /// Binds a value at a given key from a low level mapping (something that /// uses the P6mapping representation). /// </summary> /// <param name="TC"></param> /// <param name="LLMapping"></param> /// <param name="Key"></param> /// <param name="Value"></param> /// <returns></returns> public static RakudoObject llmapping_bind_at_key(ThreadContext TC, RakudoObject LLMapping, RakudoObject Key, RakudoObject Value) { if (LLMapping is P6mapping.Instance) { var Storage = ((P6mapping.Instance)LLMapping).Storage; var StrKey = Ops.unbox_str(TC, Key); if (Storage.ContainsKey(StrKey)) Storage[StrKey] = Value; else Storage.Add(StrKey, Value); return Value; } else { throw new Exception("Cannot use llmapping_bind_at_key if representation is not P6mapping"); } }
/// <summary> /// Throws the specified exception, looking for an exception handler in the /// dynamic scope. /// </summary> /// <param name="TC"></param> /// <param name="ExceptionObject"></param> /// <param name="ExceptionType"></param> /// <returns></returns> public static RakudoObject throw_dynamic(ThreadContext TC, RakudoObject ExceptionObject, RakudoObject ExceptionType) { int WantType = Ops.unbox_int(TC, ExceptionType); var CurContext = TC.CurrentContext; while (CurContext != null) { if (CurContext.StaticCodeObject != null) { var Handlers = CurContext.StaticCodeObject.Handlers; if (Handlers != null) for (int i = 0; i < Handlers.Length; i++) if (Handlers[i].Type == WantType) return Exceptions.ExceptionDispatcher.CallHandler(TC, Handlers[i].HandleBlock, ExceptionObject); } CurContext = CurContext.Caller; } Exceptions.ExceptionDispatcher.DieFromUnhandledException(TC, ExceptionObject); return null; // Unreachable; above call exits always. }
/// <summary> /// Invokes the specified exception handler with the given exception /// object. /// </summary> /// <param name="Handler"></param> /// <param name="ExceptionObject"></param> /// <returns></returns> public static RakudoObject CallHandler(ThreadContext TC, RakudoObject Handler, RakudoObject ExceptionObject) { // Invoke the handler. Note that in some cases we never return from it; // for example, the return exception handler does .leave. var Returned = Handler.STable.Invoke(TC, Handler, CaptureHelper.FormWith(new RakudoObject[] { ExceptionObject })); // So, we returned. Let's see if it's resumable. var ResumableMeth = Returned.STable.FindMethod(TC, Returned, "resumable", Hints.NO_HINT); var Resumable = ResumableMeth.STable.Invoke(TC, ResumableMeth, CaptureHelper.FormWith(new RakudoObject[] { Returned })); if (Ops.unbox_int(TC, Resumable) != 0) { // Resumable, so don't need to stack unwind. Simply return // from here. return Returned; } else { // Not resumable, so stack unwind out of the block containing // the handler. throw new LeaveStackUnwinderException( (Handler as RakudoCodeRef.Instance).OuterBlock, Returned); } }
/// <summary> /// Compares two floating point numbers for greater-than inequality. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="ResultType"></param> /// <returns></returns> public static RakudoObject greater_than_nums(ThreadContext TC, RakudoObject x, RakudoObject y) { return Ops.box_int(TC, (Ops.unbox_num(TC, x) > Ops.unbox_num(TC, y) ? 1 : 0), TC.DefaultBoolBoxType); }
/// <summary> /// Compares reference equality. /// </summary> /// <param name="TC"></param> /// <param name="x"></param> /// <param name="y"></param> /// <returns></returns> public static RakudoObject equal_refs(ThreadContext TC, RakudoObject x, RakudoObject y) { return Ops.box_int(TC, x == y ? 1 : 0, TC.DefaultBoolBoxType); }
/// <summary> /// Compares two strings for less-than inequality. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="ResultType"></param> /// <returns></returns> public static RakudoObject less_than_strs(ThreadContext TC, RakudoObject x, RakudoObject y) { return Ops.box_int(TC, String.Compare(Ops.unbox_str(TC, x), Ops.unbox_str(TC, y)) < 0 ? 1 : 0, TC.DefaultBoolBoxType); }
/// <summary> /// Compares two floating point numbers for less-than-or-equal inequality. /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="ResultType"></param> /// <returns></returns> public static RakudoObject less_than_or_equal_nums(ThreadContext TC, RakudoObject x, RakudoObject y) { return Ops.box_int(TC, (Ops.unbox_num(TC, x) <= Ops.unbox_num(TC, y) ? 1 : 0), TC.DefaultBoolBoxType); }
/// <summary> /// Creates a LeaveStackUnwinderException to target the given block /// and exit it with the specified payload. /// </summary> /// <param name="TargetBlock"></param> /// <param name="PayLoad"></param> public LeaveStackUnwinderException(RakudoCodeRef.Instance TargetBlock, RakudoObject PayLoad) { this.TargetBlock = TargetBlock; this.PayLoad = PayLoad; }
/// <summary> /// Sets up the bootstrapping setting that we use to compile the /// real setting. /// </summary> /// <param name="KnowHOW"></param> /// <returns></returns> private static Context BootstrapSetting(RakudoObject KnowHOW, RakudoObject KnowHOWAttribute) { var SettingContext = new Context(); SettingContext.LexPad = new Lexpad(new string[] { "KnowHOW", "KnowHOWAttribute", "capture", "NQPInt", "NQPNum", "NQPStr", "NQPList", "NQPCode", "list", "NQPArray", "NQPHash" }); SettingContext.LexPad.Storage = new RakudoObject[] { KnowHOW, KnowHOWAttribute, REPRRegistry.get_REPR_by_name("P6capture").type_object_for(null, null), REPRRegistry.get_REPR_by_name("P6int").type_object_for(null, null), REPRRegistry.get_REPR_by_name("P6num").type_object_for(null, null), REPRRegistry.get_REPR_by_name("P6str").type_object_for(null, null), REPRRegistry.get_REPR_by_name("P6list").type_object_for(null, null), REPRRegistry.get_REPR_by_name("RakudoCodeRef").type_object_for(null, KnowHOW.STable.REPR.instance_of(null, KnowHOW)), CodeObjectUtility.WrapNativeMethod((TC, self, C) => { var NQPList = Ops.get_lex(TC, "NQPList"); var List = NQPList.STable.REPR.instance_of(TC, NQPList) as P6list.Instance; var NativeCapture = C as P6capture.Instance; foreach (var Obj in NativeCapture.Positionals) List.Storage.Add(Obj); return List; }), null, null }; return SettingContext; }
/// <summary> /// Sorts the candidates. /// </summary> /// <param name="Unsorted"></param> /// <returns></returns> private static RakudoCodeRef.Instance[] Sort(ThreadContext TC, RakudoObject[] Unsorted) { /* Allocate results array (just allocate it for worst case, which * is no ties ever, so a null between all of them, and then space * for the terminating null. */ var NumCandidates = Unsorted.Length; var Result = new RakudoCodeRef.Instance[2 * NumCandidates + 1]; /* Create a node for each candidate in the graph. */ var Graph = new CandidateGraphNode[NumCandidates]; for (int i = 0; i < NumCandidates; i++) { Graph[i] = new CandidateGraphNode() { Candidate = (RakudoCodeRef.Instance)Unsorted[i], Edges = new CandidateGraphNode[NumCandidates] }; } /* Now analyze type narrowness of the candidates relative to each other * and create the edges. */ for (int i = 0; i < NumCandidates; i++) { for (int j = 0; j < NumCandidates; j++) { if (i == j) continue; if (IsNarrower(TC, Graph[i].Candidate, Graph[j].Candidate) != 0) { Graph[i].Edges[Graph[i].EdgesOut] = Graph[j]; Graph[i].EdgesOut++; Graph[j].EdgesIn++; } } } /* Perform the topological sort. */ int CandidatesToSort = NumCandidates; int ResultPos = 0; while (CandidatesToSort > 0) { int StartPoint = ResultPos; /* Find any nodes that have no incoming edges and add them to * results. */ for (int i = 0; i < NumCandidates; i++) { if (Graph[i].EdgesIn == 0) { /* Add to results. */ Result[ResultPos] = Graph[i].Candidate; Graph[i].Candidate = null; ResultPos++; CandidatesToSort--; Graph[i].EdgesIn = EDGE_REMOVAL_TODO; } } if (StartPoint == ResultPos) { throw new Exception("Circularity detected in multi sub types."); } /* Now we need to decrement edges in counts for things that had * edges from candidates we added here. */ for (int i = 0; i < NumCandidates; i++) { if (Graph[i].EdgesIn == EDGE_REMOVAL_TODO) { for (int j = 0; j < Graph[i].EdgesOut; j++) Graph[i].Edges[j].EdgesIn--; Graph[i].EdgesIn = EDGE_REMOVED; } } /* This is end of a tied group, so leave a gap. */ ResultPos++; } return Result; }
/// <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(ThreadContext TC, RakudoCodeRef.Instance DispatchRoutine, RakudoObject Capture) { // Extract the native capture. // XXX Handle non-native captures too. var NativeCapture = Capture as P6capture.Instance; // First, try the dispatch cache. if (DispatchRoutine.MultiDispatchCache != null && NativeCapture.Nameds == null) { var CacheResult = DispatchRoutine.MultiDispatchCache.Lookup(NativeCapture.Positionals); if (CacheResult != null) return CacheResult; } // Sort the candidates. // XXX Cache this in the future. var SortedCandidates = Sort(TC, DispatchRoutine.Dispatchees); // Now go through the sorted candidates and find the first one that // matches. var PossiblesList = new List<RakudoCodeRef.Instance>(); foreach (RakudoCodeRef.Instance Candidate in SortedCandidates) { // If we hit a null, we're at the end of a group. if (Candidate == null) { if (PossiblesList.Count == 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[0]); } return PossiblesList[0]; } else if (PossiblesList.Count > 1) { // Here is where you'd handle constraints. throw new Exception("Ambiguous dispatch: more than one candidate matches"); } else { continue; } } /* Check if it's admissible by arity. */ var NumArgs = NativeCapture.Positionals.Length; if (NumArgs < Candidate.Sig.NumRequiredPositionals || NumArgs > Candidate.Sig.NumPositionals) continue; /* Check if it's admissible by types and definedness. */ var TypeCheckCount = Math.Min(NumArgs, Candidate.Sig.NumPositionals); var TypeMismatch = false; for (int i = 0; i < TypeCheckCount; i++) { var Arg = NativeCapture.Positionals[i]; var Type = Candidate.Sig.Parameters[i].Type; if (Type != null && Ops.unbox_int(TC, Type.STable.TypeCheck(TC, Arg.STable.WHAT, Type)) == 0) { TypeMismatch = true; break; } var Definedness = Candidate.Sig.Parameters[i].Definedness; if (Definedness != DefinednessConstraint.None) { var ArgDefined = Arg.STable.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 Exception("No candidates found to dispatch to"); }
/// <summary> /// Compares two types to see if the first is narrower than the second. /// </summary> /// <returns></returns> public static bool IsNarrowerType(ThreadContext TC, RakudoObject A, RakudoObject B) { // If one of the types is null, then we know that's automatically // wider than anything. if (B == null && A != null) return true; else if (A == null || B == null) return false; // Otherwise, check with the type system. return Ops.unbox_int(TC, Ops.type_check(TC, A, B)) != 0; }
/// <summary> /// If the first passed object reference is not null, returns it. Otherwise, /// returns the second passed object reference. (Note, we should one day drop /// this and implement it as a compiler transformation, to avoid having to /// look up the thing to vivify). /// </summary> /// <param name="TC"></param> /// <param name="Check"></param> /// <param name="VivifyWith"></param> /// <returns></returns> public static RakudoObject vivify(ThreadContext TC, RakudoObject Check, RakudoObject VivifyWith) { return Check ?? VivifyWith; }
/// <summary> /// Takes a set of positional parameters and, based on their STable /// IDs and definedness, /// </summary> /// <param name="Positionals"></param> /// <returns></returns> private long[] PositionalsToTypeCacheIDs(RakudoObject[] Positionals) { var Result = new long[Positionals.Length]; for (int i = 0; i < Positionals.Length; i++) { var STable = Positionals[i].STable; Result[i] = STable.TypeCacheID | (STable.REPR.defined(null, Positionals[i]) ? 1L : 0L); } return Result; }
/// <summary> /// Does a cache lookup based on the passed positional arguments. /// If a candidate is found, returns it. Otherwise, returns null. /// </summary> /// <param name="Positionals"></param> /// <returns></returns> public RakudoObject Lookup(RakudoObject[] Positionals) { // If it's within the arity we cache... if (Positionals.Length <= MAX_ARITY) { // ...and we did cache something... var Cache = ArityCaches[Positionals.Length]; if (Cache != null && Cache.NumEntries != 0) { // Get what we're looking for. var Seeking = PositionalsToTypeCacheIDs(Positionals); // Look through the cache for it. ci = type cache array index, // ri = result list index. int ci = 0; for (int ri = 0; ri < Cache.NumEntries; ri++) { var Matched = true; for (int j = 0; j < Positionals.Length; j++) { if (Seeking[j] != Cache.TypeIDs[ci]) { Matched = false; break; } ci++; } if (Matched) return Cache.Results[ri]; } } } return null; }
/// <summary> /// Leaves the specified block, returning the specified value from it. This /// unwinds the stack. /// </summary> /// <param name="TC"></param> /// <param name="Block"></param> /// <param name="ReturnValue"></param> /// <returns></returns> public static RakudoObject leave_block(ThreadContext TC, RakudoObject Block, RakudoObject ReturnValue) { throw new Exceptions.LeaveStackUnwinderException(Block as RakudoCodeRef.Instance, ReturnValue); }