예제 #1
0
        public static void DefineGlobals()
        {
            var g = Module.Global;

            Documentation.SectionIntroduction("reflection",
                                              "Predicates that can be used by a program to reason about itself.");

            Documentation.SectionIntroduction("reflection//static analysis",
                                              "Predicates that can be used by a program to check what tasks can call what other tasks.");

            Documentation.SectionIntroduction("reflection//dynamic analysis",
                                              "Predicates that can be used by a program to check what tasks have been called in this execution path.");

            // Argument is a compound task
            g["CompoundTask"] = new GeneralPrimitive(nameof(CompoundTask), CompoundTask)
                                .Arguments("x")
                                .Documentation("type testing", "True if x is a compound task, i.e. a task defined by rules.");

            // Second arg is a method of the first arg
            Func <CompoundTask, Method, bool>          inInMode  = (t, m) => m.Task == t;
            Func <CompoundTask, IEnumerable <Method> > inOutMode = t => t.Methods;
            Func <Method, IEnumerable <CompoundTask> > outInMode = m => new[] { m.Task };

            g["TaskMethod"] =
                new GeneralPredicate <CompoundTask, Method>("TaskMethod", inInMode, inOutMode, outInMode, null)
                .Arguments("?task", "?method")
                .Documentation("reflection//static analysis", "True when ?method is a method of ?task");

            // Gets the MethodCallFrame of the most recent call
            g["LastMethodCallFrame"] = new GeneralPrimitive("LastMethodCallFrame", LastMethodCallFrame)
                                       .Arguments("?frame")
                                       .Documentation("reflection//dynamic analysis", "Sets ?frame to the reflection information for the current method call.");

            // Second argument is in the caller chain leading to the first argument
            Func <MethodCallFrame, Method, bool>          inInMode1  = (f, m) => f.CallerChain.FirstOrDefault(a => a.Method == m) != null;
            Func <MethodCallFrame, IEnumerable <Method> > inOutMode1 = f => f.CallerChain.Select(a => a.Method);

            g["CallerChainAncestor"] =
                new GeneralPredicate <MethodCallFrame, Method>("CallerChainAncestor", inInMode1, inOutMode1, null, null)
                .Arguments("frame", "?method")
                .Documentation("reflection//dynamic analysis", "True if ?method called frame's method or some other method that eventually called this frame's method.");

            // Second argument is in the goal chain leading to the first argument
            Func <MethodCallFrame, Method, bool>          inInMode2  = (f, m) => f.GoalChain.FirstOrDefault(a => a.Method == m) != null;
            Func <MethodCallFrame, IEnumerable <Method> > inOutMode2 = f => f.GoalChain.Select(a => a.Method);

            g["GoalChainAncestor"] =
                new GeneralPredicate <MethodCallFrame, Method>("GoalChainAncestor", inInMode2, inOutMode2, null, null)
                .Arguments("frame", "?method")
                .Documentation("reflection//dynamic analysis", "True if a successful call to ?method preceded this frame.");

            // First argument calls the second argument
            g["TaskCalls"] = new GeneralPrimitive(nameof(TaskCalls), TaskCalls)
                             .Arguments("?caller", "?callee")
                             .Documentation("reflection//static analysis", "True if task ?caller has a method that calls ?callee");

            // Second argument is a call expression for a call in some method of first argument.
            g["TaskSubtask"] = new GeneralPrimitive(nameof(TaskSubtask), TaskSubtask)
                               .Arguments("?task", "?call")
                               .Documentation("reflection//static analysis", "True if task ?caller has a method that contains the call ?call.");
        }
예제 #2
0
        internal static void DefineGlobals()
        {
            var g = Module.Global;

            Documentation.SectionIntroduction("control flow",
                                              "Tasks that run or otherwise control the execution of other tasks.");

            Documentation.SectionIntroduction("control flow//calling tasks",
                                              "Tasks that call another task once.");

            Documentation.SectionIntroduction("control flow//looping",
                                              "Tasks that repeatedly call other tasks.");

            Documentation.SectionIntroduction("control flow//looping//all solutions predicates",
                                              "Tasks that collect together all the solutions to a given call.");

            Documentation.SectionIntroduction("higher-order predicates",
                                              "Predicates that run other predicates.");

            g[nameof(Call)] = new GeneralPrimitive(nameof(Call), Call)
                              .Arguments("call", "extra_arguments", "...")
                              .Documentation("control flow//calling tasks", "Runs the call to the task represented in the tuple 'call'. If extra_arguments are included, they will be added to the end of the call tuple.");
            g[nameof(IgnoreOutput)] = new GeneralPrimitive(nameof(IgnoreOutput), IgnoreOutput)
                                      .Arguments("calls", "...")
                                      .Documentation("control flow//calling tasks", "Runs each of the calls, in order, but throws away their output text.");
            g[nameof(Begin)] = new GeneralPrimitive(nameof(And), Begin)
                               .Arguments("task", "...")
                               .Documentation("control flow", "Runs each of the tasks, in order.");
            g[nameof(And)] = new GeneralPrimitive(nameof(And), And)
                             .Arguments("calls", "...")
                             .Documentation("control flow", "Runs each of the calls, in order.");
            g[nameof(Or)] = new GeneralPrimitive(nameof(And), Or)
                            .Arguments("calls", "...")
                            .Documentation("control flow", "Runs each of the calls, in order until one works.");
            g[nameof(Not)] = new GeneralPrimitive(nameof(Not), Not)
                             .Arguments("call")
                             .Documentation("higher-order predicates", "Runs call.  If the call succeeds, it Not, fails, undoing any effects of the call.  If the call fails, then Not succeeds.  This requires the call to be ground (not contain any uninstantiated variables), since [Not [P ?x]] means \"not [P ?x] for any ?x\".  Use NotAny if you mean to have unbound variables in the goal.");
            g[nameof(NotAny)] = new GeneralPrimitive(nameof(NotAny), NotAny)
                                .Arguments("call")
                                .Documentation("higher-order predicates", "Runs call.  If the call succeeds, it Not, fails, undoing any effects of the call.  If the call fails, then Not succeeds.");
            g[nameof(FindAll)] = new GeneralPrimitive(nameof(FindAll), FindAll)
                                 .Arguments("?result", "call", "?all_results")
                                 .Documentation("control flow//looping//all solutions predicates", "Runs call, backtracking to find every possible solution to it.  For each solution, FindAll records the value of ?result, and returns a list of all ?results in order, in ?all_results.  If backtracking produces duplicate ?results, there will be multiple copies of them in ?all_results; to eliminate duplicate solutions, use FindUnique.  If call never fails, this will run forever.");
            g[nameof(FindUnique)] = new GeneralPrimitive(nameof(FindUnique), FindUnique)
                                    .Arguments("?result", "call", "?all_results")
                                    .Documentation("control flow//looping//all solutions predicates", "Runs call, backtracking to find every possible solution to it.  For each solution, FindAll records the value of ?result, and returns a list of all ?results in order, in ?all_results, eliminating duplicate solutions.  If call never fails, this will run forever.");
            g[nameof(DoAll)] = new DeterministicTextGeneratorMetaTask(nameof(DoAll), DoAll)
                               .Arguments("generator_call", "other_calls", "...")
                               .Documentation("control flow//looping", "Runs generator_call, finding all its solutions by backtracking.  For each solution, runs the other tasks, collecting all their text output.  Since the results are backtracked, any variable bindings or set commands are undone.");;
            g[nameof(ForEach)] = new GeneralPrimitive(nameof(ForEach), ForEach)
                                 .Arguments("generator_call", "other_calls", "...")
                                 .Documentation("control flow//looping", "Runs generator_call, finding all its solutions by backtracking.  For each solution, runs the other tasks, collecting all their text output.  Since the results are backtracked, any variable bindings are undone.  However, all text generated and set commands performed are preserved.");
            g[nameof(Implies)] = new GeneralPrimitive(nameof(Implies), Implies)
                                 .Arguments("higher-order predicates", "other_calls", "...")
                                 .Documentation("higher-order predicates", "True if for every solution to generator_call, other_calls succeeds.  So this is essentially like ForEach, but whereas ForEach always succeeds, Implies fails if other_calls ever fails.  Text output and sets of global variables are preserved, as with ForEach.");
            g[nameof(Once)] = new GeneralPrimitive(nameof(Once), Once)
                              .Arguments("code", "...")
                              .Documentation("control flow//controlling backtracking", "Runs code normally, however, if any subsequent code backtracks, once will not rerun the code, but will fail to whatever code preceded it.");
            g[nameof(ExactlyOnce)] = new GeneralPrimitive(nameof(ExactlyOnce), ExactlyOnce)
                                     .Arguments("code", "...")
                                     .Documentation("control flow//controlling backtracking", "Runs code normally.  If the code fails, ExactlyOnce throws an exception.  If it succeeds, ExactlyOnce succeeds.  However, if any subsequent code backtracks, once will not rerun the code, but will fail to whatever code preceded it.");
            g[nameof(Max)] = new GeneralPrimitive(nameof(Max), Max)
                             .Arguments("?scoreVariable", "code", "...")
                             .Documentation("control flow//looping//all solutions predicates", "Runs code, backtracking to find all solutions, keeping the state (text output and variable bindings) of the solution with the largest value of ?scoreVariable");
            g[nameof(Min)] = new GeneralPrimitive(nameof(Min), Min)
                             .Arguments("?scoreVariable", "code", "...")
                             .Documentation("control flow//looping//all solutions predicates", "Runs code, backtracking to find all solutions, keeping the state (text output and variable bindings) of the solution with the smallest value of ?scoreVariable");
            g[nameof(SaveText)] = new GeneralPrimitive(nameof(SaveText), SaveText)
                                  .Arguments("call", "?variable")
                                  .Documentation("control flow//calling tasks", "Runs call, but places its output in ?variable rather than the output buffer.");
            g[nameof(PreviousCall)] = new GeneralPrimitive(nameof(PreviousCall), PreviousCall)
                                      .Arguments("?call_pattern")
                                      .Documentation("reflection//dynamic analysis", "Unifies ?call_pattern with the most recent successful call that matches it.  Backtracking will match against previous calls.");
            g[nameof(UniqueCall)] = new GeneralPrimitive(nameof(UniqueCall), UniqueCall)
                                    .Arguments("?call_pattern")
                                    .Documentation("reflection//dynamic analysis", "Calls ?call_pattern, finding successive solutions until one is found that can't be unified with a previous successful call.");
            g[nameof(Parse)] = new GeneralPrimitive(nameof(Parse), Parse)
                               .Arguments("call", "text")
                               .Documentation("control flow//calling tasks", "True if call can generate text as its output.  This is done by running call and backtracking whenever its output diverges from text.  Used to determine if a grammar can generate a given string.");
        }
예제 #3
0
        /// <summary>
        /// Add the built-in primitives to the global module.
        /// </summary>
        internal static void DefineGlobals()
        {
            var g = Module.Global;

            Documentation.SectionIntroduction("comparison",
                                              "Predicates that test whether two values are the same or different.  Many of these use unification, in which case they are testing whether the values can be made identical through binding variables.");

            g["="] = new GeneralPrimitive("=", (args, o, e, predecessor, k) =>
            {
                ArgumentCountException.Check("=", 2, args);
                return(e.Unify(args[0], args[1], e.Unifications, out var newBindings) &&
                       k(o, newBindings, e.State, predecessor));
            }).Arguments("a", "b")
                     .Documentation("comparison", "Matches (unifies) a and b, and succeeds when they're the same.");

            g["Different"] = new SimplePredicate <object, object>("Different",
                                                                  (a, b) => !a.Equals(b) && !(a is LogicVariable) && !(b is LogicVariable))
                             .Arguments("a", "b")
                             .Documentation("comparison", "Attempts to match a and b and succeeds if they *can't* be matched");

            g[">"] = new SimplePredicate <float, float>(">", (a, b) => a > b)
                     .Arguments("a", "b")
                     .Documentation("comparison", "True when a and b are both numbers and a is larger");
            g["<"] = new SimplePredicate <float, float>("<", (a, b) => a < b)
                     .Arguments("a", "b")
                     .Documentation("comparison", "True when a and b are both numbers and a is smaller");
            g[">="] = new SimplePredicate <float, float>(">=", (a, b) => a >= b)
                      .Arguments("a", "b")
                      .Documentation("comparison", "True when a and b are both numbers and a is at least as large as b");
            g["<="] = new SimplePredicate <float, float>("<=", (a, b) => a <= b)
                      .Arguments("a", "b")
                      .Documentation("comparison", "True when a and b are both numbers and a is no larger than b");

            Documentation.SectionIntroduction("output",
                                              "Tasks that print things.");

            g["Paragraph"] = new DeterministicTextGenerator("Paragraph",
                                                            () => new[] { TextUtilities.NewParagraphToken })
                             .Arguments()
                             .Documentation("output", "Starts a new paragraph");
            g["NewLine"] = new DeterministicTextGenerator("NewLine",
                                                          () => new[] { TextUtilities.NewLineToken })
                           .Arguments()
                           .Documentation("output", "Starts a new line");
            g["FreshLine"] = new DeterministicTextGenerator("FreshLine",
                                                            () => new[] { TextUtilities.FreshLineToken })
                             .Arguments()
                             .Documentation("output", "Starts a new line, unless we're already at the start of a new line");
            g["ForceSpace"] = new DeterministicTextGenerator("ForceSpace",
                                                             () => new[] { TextUtilities.ForceSpaceToken })
                              .Arguments()
                              .Documentation("output", "Forces a space to be inserted between two tokens that wouldn't normally be separated.  For example, \"a .\" prints as \"a.\" but \"a [ForceSpace] .\" prints as \"a .\"");

            Documentation.SectionIntroduction("control flow//controlling backtracking",
                                              "Tasks that control how or whether execution backtracks.");

            g["Fail"] = new SimplePredicate("Fail", () => false)
                        .Arguments()
                        .Documentation("control flow//controlling backtracking", "Never succeeds; forces the system to backtrack immediately.");

            Documentation.SectionIntroduction("debugging",
                                              "Tasks used to help debug code.");

            g["Break"] = new SimplePredicate("Break", Break)
                         .Arguments()
                         .Documentation("debugging", "Breakpoint; pauses execution and displays the current stack in the debugger.");

            g["Throw"] = new SimpleNAryPredicate("Throw", Throw)
                         .Arguments("message", "...")
                         .Documentation("control flow", "Throws an exception (error) containing the specified message.");

            g["StringForm"] =
                new GeneralPredicate <object, string>("StringForm",
                                                      (o, s) => o.ToString() == s,
                                                      o => new [] { o.ToString() },
                                                      null, null)
                .Arguments("object", "?string_form")
                .Documentation("output", "Matches ?string_form with the printed representation of object");

            g["WriteVerbatim"] = new DeterministicTextMatcher("WriteVerbatim", (o =>
            {
                switch (o)
                {
                case null:
                    return(new[] { "null" });

                case string[] tokens:
                    return(tokens);

                default:
                    return(new[] { Writer.TermToString(o) });
                }
            }))
                                 .Arguments("object")
                                 .Documentation("output", "Prints object; _'s are printed as themselves rather than changed to spaces,");

            WritePrimitive = new DeterministicTextMatcher("Write", (o =>
            {
                switch (o)
                {
                case null:
                    return(new[] { "null" });

                case string[] tokens:
                    return(tokens.Length == 0? tokens : tokens.Skip(1).Prepend(tokens[0].Capitalize()).ToArray());

                default:
                    return(new[] { Writer.TermToString(o).Replace('_', ' ') });
                }
            }));

            g["Write"] = WritePrimitive
                         .Arguments("object")
                         .Documentation("output", "Prints object, transforming _'s to spaces");

            g["WriteCapitalized"] = new DeterministicTextMatcher("WriteCapitalized", (o =>
            {
                switch (o)
                {
                case null:
                    return(new[] { "null" });

                case string[] tokens:
                    return(tokens.Length == 0 ? tokens : tokens.Skip(1).Prepend(tokens[0].Capitalize()).ToArray());

                default:
                    return(new[] { Writer.TermToString(o).Replace('_', ' ').Capitalize() });
                }
            }))
                                    .Arguments("object")
                                    .Documentation("output", "Prints object, transforming _'s to spaces.  If the first character of the output is a lower-case letter, it will capitalize it.");

            g["WriteConcatenated"] = new DeterministicTextGenerator <object, object>("WriteConcatenated",
                                                                                     (s1, s2) => { return(new[] { $"{Writer.TermToString(s1).Replace('_', ' ')}{Writer.TermToString(s2).Replace('_', ' ')}" }); })
                                     .Arguments("object1", "object2")
                                     .Documentation("output", "Prints both objects, without a space between them, and changes and _'s to spaces.");

            Documentation.SectionIntroduction("data structures",
                                              "Predicates that create or access complex data objects.  Note that dictionaries and lists can also be used as predicates.  So [dictionary ?key ?value] is true when ?key has ?value in the dictinoary and and [list ?element] is true when ?element is an element of the list.");

            Documentation.SectionIntroduction("data structures//lists",
                                              "Predicates access lists in particular.  These work with any C# object that implements the IList interface, including Step tuples (which are the C# type object[]).");

            g["Member"] = new GeneralPredicate <object, IEnumerable <object> >("Member", (member, collection) => collection != null && collection.Contains(member), null, collection => collection ?? EmptyArray, null)
                          .Arguments("element", "collection")
                          .Documentation("data structures//lists", "True when element is an element of collection.");

            g["Length"] = new SimpleFunction <IList, int>("Length", l => l.Count)
                          .Arguments("list", "?length")
                          .Documentation("data structures//list", "True when list has exactly ?length elements");

            g["Nth"] = new GeneralNAryPredicate("Nth",
                                                args =>
            {
                ArgumentCountException.Check("Nth", 3, args);
                var list       = ArgumentTypeException.Cast <IList>("Nth", args[0], args);
                var indexVar   = args[1] as LogicVariable;
                var elementVar = args[2] as LogicVariable;

                if (indexVar == null)
                {
                    var index = ArgumentTypeException.Cast <int>("Nth", args[1], args);
                    return(new[] { new[] { list, index, list[index] } });
                }
                else if (elementVar == null)
                {
                    var elt   = args[2];
                    var index = list.IndexOf(elt);
                    if (index >= 0)
                    {
                        return new[] { new[] { list, index, args[2] } }
                    }
                    ;
                    else
                    {
                        return(new object[0][]);
                    }
                }

                throw new ArgumentInstantiationException("Nth", new BindingEnvironment(), args);
            })
                       .Arguments("list", "index", "?element")
                       .Documentation("data structures//list", "True when element of list at index is ?element");

            g["Cons"] = new GeneralNAryPredicate("Cons", args =>
            {
                ArgumentCountException.Check("Cons", 3, args);
                if (args[2] is object[] tuple)
                {
                    return new[] { new[] { tuple[0], tuple.Skip(1).ToArray(), tuple } }
                }
                ;
                if (args[1] is object[] restTuple)
                {
                    return new[] { new[] { args[0], restTuple, restTuple.Prepend(args[0]).ToArray() } }
                }
                ;
                throw new ArgumentException("Either the second or argument of Cons must be a tuple.");
            })
                        .Arguments("firstElement", "restElements", "tuple")
                        .Documentation("True when tuple starts with firstElement and continues with restElements.");

            Documentation.SectionIntroduction("metalogical",
                                              "Predicates that test the binding state of a variable.");

            g["Var"] = new SimplePredicate <object>("Var", o => o is LogicVariable)
                       .Arguments("x")
                       .Documentation("metalogical", "Succeeds when its argument is an uninstantiated variable (a variable without a value)");
            g["NonVar"] = new SimplePredicate <object>("NonVar", o => !(o is LogicVariable))
                          .Arguments("x")
                          .Documentation("metalogical", "Succeeds when its argument is a *not* an uninstantiated variable.");

            g["CopyTerm"] = new GeneralPrimitive("CopyTerm",
                                                 (args, t, b, f, k) =>
            {
                ArgumentCountException.Check("CopyTerm", 2, args);
                return(b.Unify(args[1], b.CopyTerm(args[0]), out var u) && k(t, u, b.State, f));
            })
                            .Arguments("in", "out")
                            .Documentation("metalogical",
                                           "Sets out to a copy of in with fresh variables, so that unifications to one don't affect the other");

            Documentation.SectionIntroduction("type testing",
                                              "Predicates that test what type of data object their argument is.  These fail when the argument is an unbound variable.");

            g["String"] = new SimplePredicate <object>("String", o => o is string)
                          .Arguments("x")
                          .Documentation("type testing", "Succeeds when its argument is a string");
            g["Number"] = new SimplePredicate <object>("Number", o => o is int || o is float || o is double)
                          .Arguments("x")
                          .Documentation("type testing", "Succeeds when its argument is a number");
            g["Tuple"] = new SimplePredicate <object>("Tuple", o => o is object[])
                         .Arguments("x")
                         .Documentation("type testing", "Succeeds when its argument is a tuple");
            g["BinaryTask"] = new SimplePredicate <object>("BinaryTask",
                                                           o => (o is Task c && c.ArgumentCount == 2))
                              .Arguments("x")
                              .Documentation("type testing", "Succeeds when its argument is 2-argument task");
            g["Empty"] = Cons.Empty;

            g["CountAttempts"] = new GeneralPrimitive("CountAttempts", (args, o, bindings, p, k) =>
            {
                ArgumentCountException.Check("CountAttempts", 1, args);
                ArgumentInstantiationException.Check("CountAttempts", args[0], false, bindings, args);
                int count = 0;
                while (true)
                {
                    if (k(o,
                          BindingList <LogicVariable> .Bind(bindings.Unifications, (LogicVariable)args[0], count++),
                          bindings.State,
                          p))
                    {
                        return(true);
                    }
                }
                // ReSharper disable once FunctionNeverReturns
            }).Arguments("?count")
                                 .Documentation("control flow", "Binds ?count to 0, then to increasing numbers each time the system backtracks to the call.  Used in a loop to run something repeatedly: [CountAttempts ?count] [DoSomething] [= ?count 100] will run DoSomething until ?count is 100.");

            Documentation.SectionIntroduction("randomization",
                                              "Tasks that choose random numbers or list elements.");

            g["RandomIntegerInclusive"] = new SimpleFunction <int, int, int>("RandomIntegerInclusive", Randomization.IntegerInclusive)
                                          .Arguments("min", "max", "?random")
                                          .Documentation("randomization", "Sets ?random to a random integer such that min <= ?random <= max");

            g["RandomIntegerExclusive"] = new SimpleFunction <int, int, int>("RandomIntegerExclusive", Randomization.IntegerExclusive)
                                          .Arguments("min", "max", "?random")
                                          .Documentation("randomization", "Sets ?random to a random integer such that min <= ?random < max");

            g["RandomElement"] = new GeneralPredicate <IList, object>(
                "RandomElement",
                (list, elt) => list.Contains(elt),
                list => list.BadShuffle().Cast <object>(),
                null,
                null)
                                 .Arguments("list", "?element")
                                 .Documentation("randomization", "Sets ?element to a random element of list.  If this is backtracked, it generates a random shuffle of the elements of this list.  However, not all shuffles are possible; it starts with a random element and moves to subsequent elements with a random step size.");

            Documentation.SectionIntroduction("string processing",
                                              "Predicates that test the spelling of strings.");

            g["Format"] = new SimpleFunction <string, object[], string>("Format", string.Format)
                          .Arguments("format_string, argument_list, ?formatted_string")
                          .Documentation("string processing",
                                         "True when formatted_string is the result of formatting format_string with the arguments.  This is just a wrapper for .NET's string.Format routine.");

            g["Downcase"] = new SimpleFunction <string, string>("DownCase", from =>
            {
                var b = new StringBuilder();
                foreach (var c in from)
                {
                    b.Append(char.ToLower(c));
                }
                return(b.ToString());
            })
                            .Arguments("string, ?downcased")
                            .Documentation("string processing",
                                           "True when downcased is the string with all alphabetic characters converted to lowercase.");

            g["Downcased"] = new SimpleFunction <string, string>("Downcased", from =>
            {
                var b = new StringBuilder();
                foreach (var c in from)
                {
                    b.Append(char.ToLower(c));
                }
                return(b.ToString());
            })
                             .Arguments("string, ?downcased")
                             .Documentation("string processing",
                                            "True when downcased is the string with all alphabetic characters converted to lowercase.");

            g["Upcased"] = new SimpleFunction <string, string>("Upcased", from =>
            {
                var b = new StringBuilder();
                foreach (var c in from)
                {
                    b.Append(char.ToUpper(c));
                }
                return(b.ToString());
            })
                           .Arguments("string, ?upcased")
                           .Documentation("string processing",
                                          "True when upcased is the string with all alphabetic characters converted to uppercase.");

            g["Capitalized"] = new SimpleFunction <string, string>("Downcase", from =>
            {
                var b           = new StringBuilder();
                var startOfWord = true;
                foreach (var c in from)
                {
                    b.Append(startOfWord ? char.ToUpper(c) : c);
                    startOfWord = c == ' ' || c == '_';
                }
                return(b.ToString());
            })
                               .Arguments("string, ?capitalized")
                               .Documentation("string processing",
                                              "True when capitalized is the a copy of string, which the start of each word capitalized.");


            g["StartsWithVowel"] = new SimplePredicate <object>("StartsWithVowel",
                                                                x =>
            {
                switch (x)
                {
                case string s:
                    return(StartsWithVowel(s));

                case string[] tokens:
                    return(tokens.Length > 0 && StartsWithVowel(tokens[0]));

                default:
                    return(false);
                }
            })
                                   .Arguments("string")
                                   .Documentation("string processing", "True if the string starts with a vowel.");

            g["NounSingularPlural"] =
                new GeneralPredicate <string, string>("NounSingularPlural", (s, p) => Inflection.PluralOfNoun(s) == p, s => new[] { Inflection.PluralOfNoun(s) }, p => new[] { Inflection.SingularOfNoun(p) }, null)
                .Arguments("?singular", "?plural")
                .Documentation("string processing", "True if ?plural is the English plural form of ?singular");

            Documentation.SectionIntroduction("StepRepl",
                                              "Tasks that control the behavior of StepRepl or whatever other game engine the Step code is running inside of.");

            g["EnvironmentOption"] = new SimpleNAryPredicate("EnvironmentOption",
                                                             arglist =>
            {
                ArgumentCountException.CheckAtLeast("EnvironmentOption", 1, arglist);
                var optionName = ArgumentTypeException.Cast <string>("EnvironmentOption", arglist[0], arglist);
                EnvironmentOption.Handle(optionName, arglist.Skip(1).ToArray());
                return(true);
            })
                                     .Arguments("argument", "...")
                                     .Documentation("StepRepl", "Asks StepRepl or whatever other program this Step code is running in to change its handling of step code.");

            g["Hashtable"] = new SimpleNAryFunction(
                "Hashtable",
                data =>
            {
                if ((data.Length % 2) != 0)
                {
                    throw new ArgumentException(
                        "Hashtable requires an odd number of arguments, one for the output and an equal number of keys and values");
                }
                var h = new Hashtable();
                for (var i = 0; i < data.Length; i += 2)
                {
                    h[data[i]] = data[i + 1];
                }
                return(h);
            })
                             .Arguments("?h")
                             .Documentation("data structures", "Creates a new empty hash table and stores it in ?h");

            g["Contains"] =
                new SimplePredicate <string, string>("Contains", (super, sub) => super.Contains(sub))
                .Arguments("string", "substring")
                .Documentation("string processing", "True if substring is a substring of string");

            HigherOrderBuiltins.DefineGlobals();
            ReflectionBuiltins.DefineGlobals();
            Documentation.DefineGlobals(Module.Global);
        }