private static bool ExactlyOnce(object[] args, TextBuffer output, BindingEnvironment env, MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.Check("ExactlyOnce", 1, args);
            TextBuffer finalOutput = output;
            BindingList <LogicVariable> finalBindings = null;
            State           finalState = State.Empty;
            MethodCallFrame finalFrame = predecessor;
            bool            failure    = true;

            var chain = Step.ChainFromBody("ExactlyOnce", args);

            chain.Try(output, env,
                      (o, u, d, p) =>
            {
                failure       = false;
                finalOutput   = o;
                finalBindings = u;
                finalState    = d;
                finalFrame    = p;
                return(true);
            },
                      predecessor);

            if (failure)
            {
                var failedCall = (Call)chain;
                throw new CallFailedException(env.Resolve(failedCall.Task), env.ResolveList(failedCall.Arglist));
            }
            return(k(finalOutput, finalBindings, finalState, finalFrame));
        }
Exemple #2
0
        public override bool Try(TextBuffer output, BindingEnvironment e, Continuation k,
                                 MethodCallFrame predecessor)
        {
            if (!e.TryCopyGround(Value.Eval(e), out var expValue))
            {
                // You can't set a variable to a non-ground value
                throw new ArgumentInstantiationException("set", e,
                                                         new[] { (object)GlobalVariable, Value });
            }

            if (LocalVariable == null)
            {
                return(Continue(output,
                                new BindingEnvironment(e,
                                                       e.Unifications,
                                                       e.State.Bind(GlobalVariable, expValue)),
                                k, predecessor));
            }

            if (e.Unify(LocalVariable, expValue, out var result))
            {
                return(Continue(output,
                                new BindingEnvironment(e, result, e.State),
                                k, predecessor));
            }

            return(false);
        }
        private static bool Not(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
        {
            foreach (var arg in args)
            {
                if (!Term.IsGround(arg))
                {
                    throw new ArgumentInstantiationException("Not", e, args, "Use NotAny if you intend goals that aren't ground.");
                }
            }
            // Whether the call to args below succeeded
            var success = false;

            // This always fails, since its continuation fails too
            Step.ChainFromBody("Not", args)
            .Try(o, e,
                 (newOut, newE, newK, newP) =>
            {
                // Remember that we succeeded, then fail
                success = true;
                return(false);
            },
                 predecessor);

            // If the call to args succeeded, fail; otherwise call continuation
            return(!success && k(o, e.Unifications, e.State, predecessor));
        }
        private static bool Parse(object[] args, TextBuffer output, BindingEnvironment env,
                                  MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.CheckAtLeast(nameof(Parse), 2, args);
            var call = ArgumentTypeException.Cast <object[]>(nameof(Parse), env.Resolve(args[0]), args);
            var text = ArgumentTypeException.Cast <string[]>(nameof(Parse), env.Resolve(args[1]), args);

            if (!(call[0] is Task task))
            {
                throw new InvalidOperationException(
                          "Task argument to Parse must be a compound task, i.e. a user-defined task with methods.");
            }

            var taskArgs = new object[call.Length - 1];

            var i = 0;

            for (var callIndex = 1; callIndex < call.Length; callIndex++)
            {
                taskArgs[i++] = call[callIndex];
            }

            var parseBuffer = TextBuffer.MakeReadModeTextBuffer(text);

            return(task.Call(taskArgs, parseBuffer, env, predecessor,
                             (buffer, u, s, p) => buffer.ReadCompleted && k(output, u, s, p)));
        }
        private static bool Call(object[] args, TextBuffer output, BindingEnvironment env,
                                 MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.CheckAtLeast(nameof(Call), 1, args);
            var call = ArgumentTypeException.Cast <object[]>(nameof(Call), args[0], args);

            if (!(call[0] is Task task))
            {
                throw new InvalidOperationException(
                          "Task argument to Call must be a task");
            }

            var taskArgs = new object[call.Length - 1 + args.Length - 1];

            var i = 0;

            for (var callIndex = 1; callIndex < call.Length; callIndex++)
            {
                taskArgs[i++] = call[callIndex];
            }
            for (var argsIndex = 1; argsIndex < args.Length; argsIndex++)
            {
                taskArgs[i++] = args[argsIndex];
            }

            return(task.Call(taskArgs, output, env, predecessor, k));
        }
Exemple #6
0
 /// <summary>
 /// Run any remaining steps in the chain, otherwise run the continuation.
 /// </summary>
 /// <returns>True if all steps in the chain, and the continuation are all successful.  False means we're backtracking</returns>
 protected bool Continue(TextBuffer p, BindingEnvironment e, Continuation k, MethodCallFrame predecessor)
 {
     if (Next != null)
     {
         return(Next.Try(p, e, k, predecessor));
     }
     return(k == null || k(p, e.Unifications, e.State, predecessor));
 }
Exemple #7
0
 /// <summary>
 /// Make a new binding environment with the specified components
 /// </summary>
 public BindingEnvironment(Module module, MethodCallFrame frame,
                           BindingList <LogicVariable> unifications, State state)
 {
     Module       = module;
     Frame        = frame;
     Unifications = unifications;
     State        = state;
 }
Exemple #8
0
 internal MethodCallFrame(Method method, BindingList <LogicVariable> bindings, LogicVariable[] locals, MethodCallFrame caller, MethodCallFrame predecessor)
 {
     Method             = method;
     BindingsAtCallTime = bindings;
     Locals             = locals;
     Caller             = caller;
     Predecessor        = predecessor;
     StackDepth         = predecessor?.StackDepth + 1 ?? 0;
     if (StackDepth > MaxStackDepth)
     {
         throw new StackOverflowException("Maximum interpreter stack depth in Step program");
     }
 }
        private static bool PreviousCall(object[] args, TextBuffer output, BindingEnvironment env,
                                         MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.Check(nameof(PreviousCall), 1, args);
            if (args[0] is LogicVariable)
            {
                // [PreviousCall ?var]
                foreach (var priorGoal in predecessor.GoalChain)
                {
                    var e = priorGoal.CallExpression;
                    if (env.Unify(args[0], e, out BindingList <LogicVariable> unifications) &&
                        k(output, unifications, env.State, predecessor))
                    {
                        return(true);
                    }
                }

                return(false);
            }

            // [PreviousCall [Task ?args]]
            var call = ArgumentTypeException.Cast <object[]>(nameof(PreviousCall), args[0], args);

            foreach (var priorGoal in predecessor.GoalChain)
            {
                if (priorGoal.Method.Task != call[0])
                {
                    // Don't bother making the call expression and trying to unify.
                    continue;
                }

                var e = priorGoal.CallExpression;
                if (call.Length == e.Length &&
                    env.UnifyArrays(call, e, out BindingList <LogicVariable> unifications) &&
                    k(output, unifications, env.State, predecessor))
                {
                    return(true);
                }
            }

            return(false);
        }
        private static bool Once(object[] args, TextBuffer output, BindingEnvironment env, MethodCallFrame predecessor, Step.Continuation k)
        {
            TextBuffer finalOutput = output;
            BindingList <LogicVariable> finalBindings = null;
            State           finalState = State.Empty;
            MethodCallFrame finalFrame = predecessor;
            bool            success    = false;

            GenerateSolutionsFromBody("Once", args, output, env,
                                      (o, u, d, p) =>
            {
                success       = true;
                finalOutput   = o;
                finalBindings = u;
                finalState    = d;
                finalFrame    = p;
                return(true);
            },
                                      predecessor);

            return(success && k(finalOutput, finalBindings, finalState, finalFrame));
        }
Exemple #11
0
        /// <summary>
        /// Attempt to run this method
        /// </summary>
        /// <param name="args">Arguments from the call to the method's task</param>
        /// <param name="output">Output buffer to write to</param>
        /// <param name="env">Variable binding information</param>
        /// <param name="k">Continuation to call if method succeeds</param>
        /// <param name="pre">Predecessor frame</param>
        /// <returns>True if the method and its continuation succeeded</returns>
        public bool Try(object[] args, TextBuffer output, BindingEnvironment env, MethodCallFrame pre, Step.Continuation k)
        {
            // Make stack frame for locals
            var locals = new LogicVariable[LocalVariableNames.Length];

            for (var i = 0; i < LocalVariableNames.Length; i++)
            {
                locals[i] = new LogicVariable(LocalVariableNames[i]);
            }
            var newFrame = new MethodCallFrame(this, env.Unifications, locals, env.Frame, pre);

            MethodCallFrame.CurrentFrame = newFrame;
            var newEnv = new BindingEnvironment(env, newFrame);

            if (newEnv.UnifyArrays(args, ArgumentPattern, out BindingEnvironment finalEnv))
            {
                env.Module.TraceMethod(Module.MethodTraceEvent.Enter, this, args, output, finalEnv);
                newFrame.BindingsAtCallTime = finalEnv.Unifications;
                var traceK = env.Module.Trace == null
                    ? k
                    : (newO, newU, newState, predecessor) =>
                {
                    MethodCallFrame.CurrentFrame = newFrame;
                    env.Module.TraceMethod(Module.MethodTraceEvent.Succeed, this, args, newO,
                                           new BindingEnvironment(finalEnv, newU, newState));
                    MethodCallFrame.CurrentFrame = newFrame.Caller;
                    return(k(newO, newU, newState, predecessor));
                };
                if (StepChain?.Try(output, finalEnv, traceK, newFrame) ?? traceK(output, finalEnv.Unifications, finalEnv.State, newFrame))
                {
                    return(true);
                }
            }

            MethodCallFrame.CurrentFrame = newFrame;
            env.Module.TraceMethod(Module.MethodTraceEvent.MethodFail, this, args, output, finalEnv);
            return(false);
        }
        private static bool UniqueCall(object[] args, TextBuffer output, BindingEnvironment env,
                                       MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.CheckAtLeast(nameof(UniqueCall), 1, args);
            var call = ArgumentTypeException.Cast <object[]>(nameof(PreviousCall), args[0], args);

            if (!(call[0] is Task task))
            {
                throw new InvalidOperationException(
                          "Task argument to UniqueCall must be a task.");
            }

            var taskArgs = new object[call.Length - 1 + args.Length - 1];

            var i = 0;

            for (var callIndex = 1; callIndex < call.Length; callIndex++)
            {
                taskArgs[i++] = call[callIndex];
            }
            for (var argsIndex = 1; argsIndex < args.Length; argsIndex++)
            {
                taskArgs[i++] = args[argsIndex];
            }

            var fullCall = call;

            if (args.Length > 1)
            {
                fullCall    = new object[taskArgs.Length + 1];
                fullCall[0] = task;
                for (var j = 0; j < taskArgs.Length; j++)
                {
                    fullCall[j + 1] = taskArgs[j];
                }
            }

            if (task.Call(taskArgs, output, env, predecessor,
                          (o, u, s, newPredecessor) =>
            {
                foreach (var priorGoal in predecessor.GoalChain)
                {
                    if (priorGoal.Method.Task != task)
                    {
                        // Don't bother making the call expression and trying to unify.
                        continue;
                    }

                    if (env.Unify(fullCall, priorGoal.CallExpression, u, out BindingList <LogicVariable> _))
                    {
                        // We already did a call that matches this call
                        // So have the continuation return false, forcing the task.Call above to backtrack
                        // and try to generate a new solution
                        return(false);
                    }
                }

                return(k(o, u, s, newPredecessor));
            }))
            {
                return(true);
            }

            return(false);
        }
Exemple #13
0
        public override bool Try(TextBuffer output, BindingEnvironment e, Continuation k, MethodCallFrame predecessor)
        {
            foreach (var branch in EffectiveBranches)
            {
                if (branch == null)  // Empty branch, e.g. [case ?x] Something : Something [else] [end]
                {
                    if (Continue(output, e, k, predecessor))
                    {
                        return(true);
                    }
                }
                else if (branch.Try(output, e,
                                    (o, u, d, newP) =>
                                    Continue(o, new BindingEnvironment(e, u, d), k, newP)
                                    , predecessor))
                {
                    return(true);
                }
            }

            return(false);
        }
        /// <summary>
        /// Find all solutions to the specified sequence of calls.  Return a list of the text outputs of each solution.
        /// </summary>
        internal static List <string[]> AllSolutionTextFromBody(string callingTaskName, object[] body, TextBuffer o,
                                                                BindingEnvironment e, MethodCallFrame predecessor)
        {
            var results       = new List <string[]>();
            var initialLength = o.Length;

            GenerateSolutionsFromBody(callingTaskName, body, o, e,
                                      (output, b, d, p) =>
            {
                var chunk = new string[output.Length - initialLength];
                for (var i = initialLength; i < output.Length; i++)
                {
                    chunk[i - initialLength] = o.Buffer[i];
                }
                results.Add(chunk);
                return(false);
            },
                                      predecessor);
            return(results);
        }
 private static bool Or(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
 => args.Any(call =>
 {
     var tuple = ArgumentTypeException.Cast <object[]>("Or", call, args);
     return(Eval(tuple, o, e, predecessor, k, "Or"));
 });
 /// <summary>
 /// Calls a task with the specified arguments and allows the user to provide their own continuation.
 /// The only (?) use case for this is when you want to forcibly generate multiple solutions
 /// </summary>
 internal static void GenerateSolutions(string taskName, object[] args, TextBuffer o, BindingEnvironment e, Step.Continuation k, MethodCallFrame predecessor)
 {
     new Call(StateVariableName.Named(taskName), args, null).Try(o, e, k, predecessor);
 }
 /// <summary>
 /// Calls all the tasks in the body and allows the user to provide their own continuation.
 /// The only (?) use case for this is when you want to forcibly generate multiple solutions
 /// </summary>
 internal static void GenerateSolutionsFromBody(string callingTaskName, object[] body, TextBuffer o, BindingEnvironment e,
                                                Step.Continuation k,
                                                MethodCallFrame predecessor)
 {
     Step.ChainFromBody(callingTaskName, body).Try(o, e, k, predecessor);
 }
        private static bool SaveText(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.Check("SaveText", 2, args);

            var textVar = e.Resolve(args[1]);

            if (textVar == null)
            {
                throw new ArgumentInstantiationException("SaveText", e, args);
            }

            var invocation = args[0] as object[];

            if (invocation == null || invocation.Length == 0)
            {
                throw new ArgumentTypeException("SaveText", typeof(Call), args[0], args);
            }
            var arglist = new object[invocation.Length - 1];

            Array.Copy(invocation, 1, arglist, 0, arglist.Length);
            var call          = new Call(invocation[0], arglist, null);
            var initialLength = o.Length;

            string[] chunk = null;
            var      frame = predecessor;

            if (call.Try(o, e,
                         (output, b, d, p) =>
            {
                frame = p;
                chunk = new string[output.Length - initialLength];
                Array.Copy(o.Buffer, initialLength, chunk, 0, output.Length - initialLength);
                return(true);
            },
                         predecessor) &&
                e.Unify(textVar, chunk, e.Unifications, out var newUnifications))
            {
                return(k(o, newUnifications, e.State, frame));
            }

            return(false);
        }
 private static bool IgnoreOutput(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
 {
     return(Step.ChainFromBody("IgnoreOutput", args).Try(
                o, e,
                (_, u, s, p) => k(o, u, s, p),
                predecessor));
 }
        private static bool NotAny(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
        {
            // Whether the call to args below succeeded
            var success = false;

            // This always fails, since its continuation fails too
            Step.ChainFromBody("NotAny", args)
            .Try(o, e,
                 (newOut, newE, newK, newP) =>
            {
                // Remember that we succeeded, then fail
                success = true;
                return(false);
            },
                 predecessor);

            // If the call to args succeeded, fail; otherwise call continuation
            return(!success && k(o, e.Unifications, e.State, predecessor));
        }
 private static IEnumerable <string> DoAll(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor)
 => AllSolutionTextFromBody("DoAll", args, o, e, predecessor).SelectMany(strings => strings);
Exemple #22
0
 /// <summary>
 /// Make a new binding environment based on the specified environment, with the specified change(s)
 /// </summary>
 internal BindingEnvironment(BindingEnvironment e, MethodCallFrame frame)
     : this(e.Module, frame, e.Unifications, e.State)
 {
 }
        private static bool FindUnique(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
        {
            ArgumentCountException.Check("FindUnique", 3, args);
            var solution = args[0];
            var call     = args[1] as object[];

            if (call == null || call.Length == 0)
            {
                throw new ArgumentException("Invalid goal expression");
            }
            var task     = ArgumentTypeException.Cast <Task>("FindUnique", call[0], args);
            var taskArgs = call.Skip(1).ToArray();

            var result    = args[2];
            var resultSet = new HashSet <object>();

            task.Call(taskArgs, o, e, predecessor, (newO, u, s, p) =>
            {
                resultSet.Add(e.Resolve(solution, u));
                return(false);
            });
            return(e.Unify(result, resultSet.ToArray(), out var final) &&
                   k(o, final, e.State, predecessor));
        }
        private static bool Implies(object[] args, TextBuffer output, BindingEnvironment env, MethodCallFrame predecessor, Step.Continuation k)
        {
            if (args.Length < 2)
            {
                throw new ArgumentCountException(nameof(Implies), 2, args);
            }

            var producer      = args[0];
            var producerChain = Step.ChainFromBody(nameof(Implies), producer);
            var consumer      = args.Skip(1).ToArray();
            var consumerChain = Step.ChainFromBody(nameof(Implies), consumer);

            var dynamicState = env.State;
            var resultOutput = output;
            var allTrue      = true;

            producerChain.Try(resultOutput, env,
                              (o, u, d, p) =>
            {
                // We've got a solution to the producer in u.
                // So run the consumer once with u but not d or o.
                allTrue &= consumerChain.Try(resultOutput,
                                             new BindingEnvironment(env, u, dynamicState),
                                             (o2, u2, d2, newP) =>
                {
                    // Save modifications to dynamic state, output; throw away binding state
                    dynamicState = d2;
                    resultOutput = o2;
                    // Accept this one solution to consumer; don't backtrack it.
                    return(true);
                },
                                             p);
                // Backtrack to generate the next solution for producer
                return(false);
            },
                              predecessor);

            // Use original unifications but accumulated output and state.
            return(allTrue && k(resultOutput, env.Unifications, dynamicState, predecessor));
        }
Exemple #25
0
 /// <summary>
 /// Make a new binding environment based on the specified environment, with the specified change(s)
 /// </summary>
 internal BindingEnvironment(Module module, MethodCallFrame frame)
     : this(module, frame, null, State.Empty)
 {
 }
Exemple #26
0
        /// <summary>
        /// Attempt to run this task
        /// </summary>
        /// <param name="output">Output to which to write text</param>
        /// <param name="env">Variable binding information</param>
        /// <param name="k">Continuation to call at the end of this step's step-chain</param>
        /// <param name="predecessor">Predecessor frame</param>
        /// <returns>True if this steps, the rest of its step-chain, and the continuation all succeed.</returns>
        public override bool Try(TextBuffer output, BindingEnvironment env, Continuation k, MethodCallFrame predecessor)
        {
            MethodCallFrame.CurrentFrame = env.Frame;
            var target  = env.Resolve(Task);
            var arglist = env.ResolveList(Arglist);

            return(CallTask(output, env, k, target, arglist, target, predecessor));
        }
Exemple #27
0
        public override bool Try(TextBuffer output, BindingEnvironment e, Continuation k, MethodCallFrame predecessor)
        {
            var collectionValue = e.Resolve(collectionVariable);

            switch (collectionValue)
            {
            case Cons list:
            {
                return(list != Cons.Empty &&
                       e.Unify(element, list.First, out var bindings) &&
                       Continue(output,
                                new BindingEnvironment(e,
                                                       bindings,
                                                       e.State.Bind(collectionVariable, list.Rest)),
                                k, predecessor));
            }

            case ImmutableStack <object> stack:
            {
                return(!stack.IsEmpty &&
                       e.Unify(element, stack.Peek(), out var bindings) &&
                       Continue(output,
                                new BindingEnvironment(e,
                                                       bindings,
                                                       e.State.Bind(collectionVariable, stack.Pop())),
                                k, predecessor));
            }

            case ImmutableQueue <object> queue:
            {
                return(!queue.IsEmpty &&
                       e.Unify(element, queue.Peek(), out var bindings) &&
                       Continue(output,
                                new BindingEnvironment(e,
                                                       bindings,
                                                       e.State.Bind(collectionVariable, queue.Dequeue())),
                                k, predecessor));
            }

            default:
                throw new ArgumentTypeException("removeNext", typeof(Cons), collectionValue,
                                                new[] { "removeNext", collectionValue });
            }
        }
 private static bool Min(object[] args, TextBuffer o, BindingEnvironment e, MethodCallFrame predecessor, Step.Continuation k)
 {
     return(MaxMinDriver("Min", args, -1, o, e, k, predecessor));
 }
Exemple #29
0
        private bool CallTask(TextBuffer output, BindingEnvironment env, Continuation k, object target, object[] arglist,
                              object originalTarget, MethodCallFrame predecessor)
        {
            switch (target)
            {
            case Task p:
                return(p.Call(arglist, output, env, predecessor,
                              (newOutput, u, s, newPredecessor) => Continue(newOutput, new BindingEnvironment(env, u, s), k, newPredecessor)));

            case string[] text:
                return(Continue(output.Append(text), env, k, predecessor));

            case IDictionary d:
                ArgumentCountException.Check(d, 2, arglist);
                var arg0 = arglist[0];
                var v0   = arg0 as LogicVariable;
                var arg1 = arglist[1];
                if (v0 == null)
                {
                    return(d.Contains(arg0) &&
                           env.Unify(arg1, d[arg0], out var u) &&
                           Continue(output,
                                    new BindingEnvironment(env, u, env.State), k, predecessor));
                }
                else
                {
                    // Arg 0 is out
                    foreach (DictionaryEntry e in d)
                    {
                        if (env.Unify(arg0, e.Key, out var unif1) &&
                            env.Unify(arg1, e.Value, unif1, out var unif2) &&
                            Continue(output, new BindingEnvironment(env, unif2, env.State), k, predecessor))
                        {
                            return(true);
                        }
                    }
                }
                return(false);

            case IList l:
                // If it's a list in the operator position, pretend it's a call to member
                if (arglist.Length != 1)
                {
                    throw new ArgumentCountException("<list member>", 1, arglist);
                }

                if (arglist[0] is LogicVariable l0)
                {
                    foreach (var e in l)
                    {
                        if (Continue(output,
                                     new BindingEnvironment(env, BindingList <LogicVariable> .Bind(env.Unifications, l0, e),
                                                            env.State),
                                     k,
                                     predecessor))
                        {
                            return(true);
                        }
                    }
                }
                else
                {
                    if (l.Contains(arglist[0]) && Continue(output, env, k, predecessor))
                    {
                        return(true);
                    }
                }
                return(false);

            case LogicVariable v:
                throw new ArgumentException($"Attempt to call an unbound variable {v}");

            case null:
                throw new ArgumentException($"Null is not a valid task in call {CallSourceText(originalTarget, arglist)}");

            case bool b:
                if (arglist.Length != 0)
                {
                    throw new ArgumentCountException(b, 0, arglist);
                }
                return(b && Continue(output, env, k, predecessor));

            default:
                if (arglist.Length == 0)
                {
                    var hook = env.Module.FindTask(MentionHook, 1, false);
                    if (hook != null)
                    {
                        return(MakeCall(hook, target, Next).Try(output, env, k, predecessor));
                    }

                    return(Continue(output.Append(target.ToString()), env, k, predecessor));
                }

                throw new ArgumentException($"Unknown task {target} in call {CallSourceText(originalTarget, arglist)}");
            }
        }
        /// <summary>
        /// Core implementation of both Max and Min
        /// </summary>
        private static bool MaxMinDriver(string taskName, object[] args,
                                         int multiplier, TextBuffer o, BindingEnvironment e,
                                         Step.Continuation k,
                                         MethodCallFrame predecessor)
        {
            var scoreVar = args[0] as LogicVariable;

            if (scoreVar == null)
            {
                throw new ArgumentInstantiationException(taskName, e, args);
            }

            var           bestScore  = multiplier * float.NegativeInfinity;
            var           bestFrame  = predecessor;
            CapturedState bestResult = new CapturedState();
            var           gotOne     = false;

            GenerateSolutionsFromBody(taskName, args.Skip(1).ToArray(), o, e,
                                      (output, u, d, p) =>
            {
                gotOne = true;

                var env = new BindingEnvironment(e, u, d);

                var maybeScore = env.Resolve(scoreVar);
                float score;
                switch (maybeScore)
                {
                case int i:
                    score = i;
                    break;

                case float f:
                    score = f;
                    break;

                case double df:
                    score = (float)df;
                    break;

                case LogicVariable _:
                    throw new ArgumentInstantiationException(taskName, new BindingEnvironment(e, u, d), args);

                default:
                    throw new ArgumentTypeException(taskName, typeof(float), maybeScore, args);
                }

                if (multiplier * score > multiplier * bestScore)
                {
                    bestScore  = score;
                    bestResult = new CapturedState(o, output, u, d);
                    bestFrame  = p;
                }

                // Always ask for another solution
                return(false);
            },
                                      predecessor);

            // When we get here, we've iterated through all solutions and kept the best one.
            // So pass it on to our continuation
            return(gotOne &&
                   k(o.Append(bestResult.Output), bestResult.Bindings, bestResult.State, bestFrame));
        }