LoadFile() public method

public LoadFile ( string fileName, bool showTokens = false, bool showParseTree = false, string onlyConsiderNode = null ) : void
fileName string
showTokens bool
showParseTree bool
onlyConsiderNode string
return void
コード例 #1
0
        public static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                ShowHelpAndExit();
            }
            bool   showTokens            = false;
            bool   showParseTree         = false;
            bool   waitForLines          = false;
            string onlyConsiderNode      = null;
            bool   showDebugging         = false;
            int    runTimes              = 1;
            bool   compileToBytecodeOnly = false;

            var    inputFiles = new List <string> ();
            string startNode  = Dialogue.DEFAULT_START;

            string[] allowedExtensions = { ".node", ".json" };

            var defaultVariables = new Dictionary <string, float> ();

            foreach (var arg in args)
            {
                // Handle 'start' parameter
                if (arg.IndexOf("-s=") != -1)
                {
                    var startArray = arg.Split(new char[] { '=' });
                    if (startArray.Length != 2)
                    {
                        ShowHelpAndExit();
                    }
                    else
                    {
                        startNode = startArray [1];
                        continue;
                    }
                }

                // Handle variable input
                if (arg.IndexOf("-v") != -1)
                {
                    var variable      = arg.Substring(2);
                    var variableArray = variable.Split(new char[] { '=' });
                    if (variableArray.Length != 2)
                    {
                        ShowHelpAndExit();
                    }
                    else
                    {
                        var varName  = "$" + variableArray [0];
                        var varValue = float.Parse(variableArray [1]);
                        defaultVariables [varName] = varValue;
                        continue;
                    }
                }

                // Handle 'only this node' parameter
                if (arg.IndexOf("-o=") != -1)
                {
                    var startArray = arg.Split(new char[] { '=' });
                    if (startArray.Length != 2)
                    {
                        ShowHelpAndExit();
                    }
                    else
                    {
                        onlyConsiderNode = startArray [1];
                        continue;
                    }
                }

                // Handle 'run times' parameter
                if (arg.IndexOf("-r=") != -1)
                {
                    var argArray = arg.Split('=');
                    if (argArray.Length != 2)
                    {
                        ShowHelpAndExit();
                    }
                    else
                    {
                        runTimes = int.Parse(argArray [1]);
                        continue;
                    }
                }


                switch (arg)
                {
                case "-t":
                    showTokens    = true;
                    showDebugging = true;
                    break;

                case "-p":
                    showParseTree = true;
                    showDebugging = true;
                    break;

                case "-w":
                    waitForLines = true;
                    break;

                case "-d":
                    showDebugging = true;
                    break;

                case "-c":
                    compileToBytecodeOnly = true;
                    break;

                case "-h":
                    ShowHelpAndExit();
                    break;

                default:

                    // only allow one file
                    if (inputFiles.Count > 0)
                    {
                        Console.Error.WriteLine("Error: Too many files specified.");
                        Environment.Exit(1);
                    }

                    var extension = System.IO.Path.GetExtension(arg);
                    if (Array.IndexOf(allowedExtensions, extension) != -1)
                    {
                        inputFiles.Add(arg);
                    }
                    break;
                }
            }

            if (inputFiles.Count == 0)
            {
                Console.Error.WriteLine("Error: No files specified.");
                Environment.Exit(1);
            }

            // Create the object that handles callbacks
            var impl = new ConsoleRunnerImplementation(waitForLines: waitForLines);

            // load the default variables we got on the command line
            foreach (var variable in defaultVariables)
            {
                impl.SetNumber(variable.Key, variable.Value);
            }

            // Load nodes
            var dialogue = new Dialogue(impl);


            // Add some methods for testing
            dialogue.library.RegisterFunction("add_three_operands", 3, delegate(Value[] parameters) {
                var f1 = parameters[0].AsNumber;
                var f2 = parameters[1].AsNumber;
                var f3 = parameters[2].AsNumber;

                return(f1 + f2 + f3);
            });

            dialogue.library.RegisterFunction("last_value", -1, delegate(Value[] parameters) {
                // return the last value
                return(parameters[parameters.Length - 1]);
            });

            dialogue.library.RegisterFunction("is_even", 1, delegate(Value[] parameters) {
                return((int)parameters[0].AsNumber % 2 == 0);
            });

            // Register the "assert" function, which stops execution if its parameter evaluates to false
            dialogue.library.RegisterFunction("assert", 1, delegate(Value[] parameters) {
                if (parameters[0].AsBool == false)
                {
                    // TODO: Include file, node and line number
                    dialogue.LogErrorMessage("ASSERTION FAILED");
                    Environment.Exit(1);
                }
            });


            // Register a function to let test scripts register how many
            // options they expect to send
            dialogue.library.RegisterFunction("prepare_for_options", 2, delegate(Value[] parameters) {
                impl.numberOfExpectedOptions = (int)parameters [0].AsNumber;
                impl.autoSelectOptionNumber  = (int)parameters[1].AsNumber;
            });

            dialogue.library.RegisterFunction("expect_line", 1, delegate(Value[] parameters) {
                impl.expectedNextLine = parameters[0].AsString;
            });

            dialogue.library.RegisterFunction("expect_command", 1, delegate(Value[] parameters) {
                impl.expectedNextCommand = parameters[0].AsString;
            });

            // If debugging is enabled, log debug messages; otherwise, ignore them
            if (showDebugging)
            {
                dialogue.LogDebugMessage = delegate(string message) {
                    Console.WriteLine("Debug: " + message);
                };
            }
            else
            {
                dialogue.LogDebugMessage = delegate(string message) {};
            }

            dialogue.LogErrorMessage = delegate(string message) {
                Console.WriteLine("ERROR: " + message);
            };

            dialogue.LoadFile(inputFiles [0], showTokens, showParseTree, onlyConsiderNode);

            if (compileToBytecodeOnly)
            {
                var result = dialogue.Compile();
                Console.WriteLine(result);
            }

            // Only run the program when we're not emitting debug output of some kind
            var runProgram =
                showTokens == false &&
                showParseTree == false &&
                compileToBytecodeOnly == false;

            if (runProgram)
            {
                // Run the conversation

                for (int run = 0; run < runTimes; run++)
                {
                    foreach (var step in dialogue.Run(startNode))
                    {
                        // It can be one of three types: a line to show, options
                        // to present to the user, or an internal command to run

                        if (step is Dialogue.LineResult)
                        {
                            var lineResult = step as Dialogue.LineResult;
                            impl.RunLine(lineResult.line);
                        }
                        else if (step is Dialogue.OptionSetResult)
                        {
                            var optionsResult = step as Dialogue.OptionSetResult;
                            impl.RunOptions(optionsResult.options, optionsResult.setSelectedOptionDelegate);
                        }
                        else if (step is Dialogue.CommandResult)
                        {
                            var commandResult = step as Dialogue.CommandResult;
                            impl.RunCommand(commandResult.command.text);
                        }
                    }
                    impl.DialogueComplete();
                }
            }
        }
コード例 #2
0
        static internal int AddLines(AddLabelsOptions options)
        {
            YarnSpinnerConsole.CheckFileList(options.files, YarnSpinnerConsole.ALLOWED_EXTENSIONS);

            var existingKeys = new List <string>();

            foreach (var file in options.files)
            {
                NodeFormat fileFormat;
                try
                {
                    fileFormat = Loader.GetFormatFromFileName(file);
                }
                catch (FormatException)
                {
                    fileFormat = NodeFormat.Unknown;
                }

                switch (fileFormat)
                {
                case NodeFormat.JSON:
                    break;

                case NodeFormat.Text:
                    break;

                default:
                    YarnSpinnerConsole.Warn(string.Format(CultureInfo.CurrentCulture, "Skipping file {0}, which is in unsupported format '{1}'", file, fileFormat));
                    continue;
                }


                Dialogue d = YarnSpinnerConsole.CreateDialogueForUtilities();

                try
                {
                    // First, we need to ensure that this file compiles.
                    d.LoadFile(file);
                }
#pragma warning disable CA1031 // Do not catch general exception types
                catch
                {
                    YarnSpinnerConsole.Warn(string.Format(CultureInfo.CurrentCulture, "Skipping file {0} due to compilation errors.", file));
                    continue;
                }
#pragma warning restore CA1031 // Do not catch general exception types

                Dictionary <string, LineInfo> linesWithNoTag = new Dictionary <string, LineInfo>();

                // Filter the string info table to exclude lines that have a line code
                foreach (var entry in d.GetStringInfoTable())
                {
                    if (entry.Key.StartsWith("line:", StringComparison.InvariantCulture) == false)
                    {
                        linesWithNoTag[entry.Key] = entry.Value;
                    }
                }

                if (linesWithNoTag.Count == 0)
                {
                    var message = string.Format(CultureInfo.CurrentCulture, "{0} had no untagged lines. Either they're all tagged already, or it has no localisable text.", file);
                    YarnSpinnerConsole.Note(message);
                    continue;
                }

                // We also need the raw NodeInfo structures contained within this file.
                // These contain the source code to what we just compiled.

                Loader.NodeInfo[] nodeInfoList;

                var nodeFormat = Loader.GetFormatFromFileName(file);

                using (var reader = new System.IO.StreamReader(file))
                {
                    nodeInfoList = d.loader.GetNodesFromText(reader.ReadToEnd(), nodeFormat);
                }

                // Convert this list into an easier-to-index dictionary
                var lineInfo = new Dictionary <string, Loader.NodeInfo>();

                foreach (var node in nodeInfoList)
                {
                    lineInfo[node.title] = node;
                }

                // Make a list of line codes that we already know about.
                // This list will be updated as we tag lines, to prevent collisions.
                existingKeys.AddRange(lineInfo.Keys);

                bool anyNodesModified = false;

                // We now have a list of all strings that do not have a string tag.
                // Add a new tag to these lines.
                foreach (var line in linesWithNoTag)
                {
                    // TODO: There's quite a bit of redundant work done here in each loop.
                    // We're unzipping and re-combining the node for EACH line. Would be better
                    // to do that only once.

                    // Get the node that this line is in.
                    var nodeInfo = lineInfo[line.Value.nodeName];

                    // Is this line contained within a rawText node?
                    if (nodeInfo.tagsList.FindIndex(i => i == "rawText") != -1)
                    {
                        // We don't need to add a tag to it - genstrings will export
                        // the whole thing for us.
                        continue;
                    }

                    // If we have a tag to consider, and this node doesn't have that tag,
                    // continue
                    if (options.onlyUseTag != null)
                    {
                        if (nodeInfo.tagsList.FindIndex(i => i == options.onlyUseTag) == -1)
                        {
                            continue;
                        }
                    }


                    // Split this node's source by newlines
                    var lines = nodeInfo.body.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);

                    // Get the original line
                    var existingLine = lines[line.Value.lineNumber - 1];

                    // Generate a new tag for this line
                    var newTag = GenerateString(existingKeys);

                    // Remember that we've used this tag, to prevent it from being re-used
                    existingKeys.Add(newTag);

                    if (options.verbose)
                    {
                        YarnSpinnerConsole.Note(string.Format(CultureInfo.CurrentCulture, "Tagged line with ID \"{0}\" in node {1}: {2}", newTag, nodeInfo.title, existingLine));
                    }

                    // Re-write the line.
                    var newLine = string.Format(CultureInfo.InvariantCulture, "{0} #{1}", existingLine, newTag);
                    lines[line.Value.lineNumber - 1] = newLine;


                    // Pack this all back up into a single string
                    nodeInfo.body = string.Join("\n", lines);

                    // Put the updated node in the node collection.
                    lineInfo[line.Value.nodeName] = nodeInfo;

                    anyNodesModified = true;
                }

                // If we didn't end up changing anything, don't modify the file
                if (anyNodesModified == false)
                {
                    continue;
                }

                // All the nodes have been updated; save this back to disk.

                // Are we doing a dry run?
                if (options.dryRun)
                {
                    // Then bail out at this point, before we start
                    // modifying files
                    YarnSpinnerConsole.Note("Would have written to file " + file);
                    continue;
                }

                var format = Loader.GetFormatFromFileName(file);

                switch (format)
                {
                case NodeFormat.JSON:
                    break;

                case NodeFormat.Text:
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }


                // Convert the nodes into the correct format
                var savedData = FileFormatConverter.ConvertNodes(lineInfo.Values, fileFormat);


                // Write the file!
                using (var writer = new System.IO.StreamWriter(file))
                {
                    writer.Write(savedData);
                }
            }
            return(0);
        }
コード例 #3
0
ファイル: Main.cs プロジェクト: Johnicholas/YarnSpinner
        public static void Main(string[] args)
        {
            if (args.Length == 0) {
                ShowHelpAndExit ();
            }
            bool showTokens = false;
            bool showParseTree = false;
            bool waitForLines = false;
            string onlyConsiderNode = null;
            bool showDebugging = false;
            int runTimes = 1;

            var inputFiles = new List<string> ();
            string startNode = Dialogue.DEFAULT_START;

            string[] allowedExtensions = {".node", ".json" };

            var defaultVariables = new Dictionary<string,float> ();

            foreach (var arg in args) {

                // Handle 'start' parameter
                if (arg.IndexOf("-s=") != -1) {
                    var startArray = arg.Split (new char[]{ '=' });
                    if (startArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        startNode = startArray [1];
                        continue;
                    }
                }

                // Handle variable input
                if (arg.IndexOf("-v") != -1) {
                    var variable = arg.Substring (2);
                    var variableArray = variable.Split (new char[]{ '=' });
                    if (variableArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        var varName = "$" + variableArray [0];
                        var varValue = float.Parse (variableArray [1]);
                        defaultVariables [varName] = varValue;
                        continue;
                    }

                }

                // Handle 'only this node' parameter
                if (arg.IndexOf("-o=") != -1) {
                    var startArray = arg.Split (new char[]{ '=' });
                    if (startArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        onlyConsiderNode = startArray [1];
                        continue;
                    }
                }

                // Handle 'run times' parameter
                if (arg.IndexOf("-r=") != -1) {
                    var argArray = arg.Split ('=');
                    if (argArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        runTimes = int.Parse (argArray [1]);
                        continue;
                    }

                }

                switch (arg) {
                case "-t":
                    showTokens = true;
                    showDebugging = true;
                    break;
                case "-p":
                    showParseTree = true;
                    showDebugging = true;
                    break;
                case "-w":
                    waitForLines = true;
                    break;
                case "-d":
                    showDebugging = true;
                    break;
                case "-h":
                    ShowHelpAndExit ();
                    break;
                default:

                    // only allow one file
                    if (inputFiles.Count > 0) {
                        Console.Error.WriteLine ("Error: Too many files specified.");
                        Environment.Exit (1);
                    }

                    var extension = System.IO.Path.GetExtension (arg);
                    if (Array.IndexOf(allowedExtensions, extension) != -1) {
                        inputFiles.Add (arg);
                    }
                    break;
                }
            }

            if (inputFiles.Count == 0) {
                Console.Error.WriteLine ("Error: No files specified.");
                Environment.Exit (1);
            }

            // Create the object that handles callbacks
            var impl = new ConsoleRunnerImplementation (waitForLines:waitForLines);

            // load the default variables we got on the command line
            foreach (var variable in defaultVariables) {

                impl.SetNumber (variable.Key, variable.Value);
            }

            // Load nodes
            var dialogue = new Dialogue(impl);

            // Add some methods for testing
            dialogue.library.RegisterFunction ("add_three_operands", 3, delegate(Value[] parameters) {
                var f1 = parameters[0].AsNumber;
                var f2 = parameters[1].AsNumber;
                var f3 = parameters[2].AsNumber;

                return f1+f2+f3;
            });

            dialogue.library.RegisterFunction ("last_value", -1, delegate(Value[] parameters) {
                // return the last value
                return parameters[parameters.Length-1];
            });

            dialogue.library.RegisterFunction ("is_even", 1, delegate(Value[] parameters) {
                return (int)parameters[0].AsNumber % 2 == 0;
            });

            // If debugging is enabled, log debug messages; otherwise, ignore them
            if (showDebugging) {
                dialogue.LogDebugMessage = delegate(string message) {
                    Console.WriteLine ("Debug: " + message);
                };
            } else {
                dialogue.LogDebugMessage = delegate(string message) {};
            }

            dialogue.LogErrorMessage = delegate(string message) {
                Console.WriteLine ("ERROR: " + message);
            };

            dialogue.LoadFile (inputFiles [0],showTokens, showParseTree, onlyConsiderNode);

            if (showTokens == false && showParseTree == false) {
                // Run the conversation

                for (int run = 0; run < runTimes; run++) {
                    foreach (var step in dialogue.Run (startNode)) {

                        // It can be one of three types: a line to show, options
                        // to present to the user, or an internal command to run

                        if (step is Dialogue.LineResult) {
                            var lineResult = step as Dialogue.LineResult;
                            impl.RunLine (lineResult.line);
                        } else if (step is Dialogue.OptionSetResult) {
                            var optionsResult = step as Dialogue.OptionSetResult;
                            impl.RunOptions (optionsResult.options, optionsResult.setSelectedOptionDelegate);
                        } else if (step is Dialogue.CommandResult) {
                            var commandResult = step as Dialogue.CommandResult;
                            impl.RunCommand (commandResult.command.text);
                        }
                    }
                    impl.DialogueComplete ();
                }

            }
        }
コード例 #4
0
ファイル: Main.cs プロジェクト: wzzwzz687510/YarnSpinner
        static internal Dialogue CreateDialogue(ExecutionOptions options, ConsoleRunnerImplementation impl)
        {
            // Load nodes
            var dialogue = new Dialogue(impl);

            // Add some methods for testing
            dialogue.library.RegisterFunction("add_three_operands", 3, delegate(Value[] parameters)
            {
                return(parameters[0] + parameters[1] + parameters[2]);
            });

            dialogue.library.RegisterFunction("last_value", -1, delegate(Value[] parameters)
            {
                // return the last value
                return(parameters[parameters.Length - 1]);
            });

            dialogue.library.RegisterFunction("is_even", 1, delegate(Value[] parameters)
            {
                return((int)parameters[0].AsNumber % 2 == 0);
            });

            // Register the "assert" function, which stops execution if its parameter evaluates to false
            dialogue.library.RegisterFunction("assert", -1, delegate(Value[] parameters)
            {
                if (parameters[0].AsBool == false)
                {
                    // TODO: Include file, node and line number
                    if (parameters.Length > 1 && parameters[1].AsBool)
                    {
                        dialogue.LogErrorMessage("ASSERTION FAILED: " + parameters[1].AsString);
                    }
                    else
                    {
                        dialogue.LogErrorMessage("ASSERTION FAILED");
                    }
                    Environment.Exit(1);
                }
            });


            // Register a function to let test scripts register how many
            // options they expect to send
            dialogue.library.RegisterFunction("prepare_for_options", 2, delegate(Value[] parameters)
            {
                impl.numberOfExpectedOptions = (int)parameters[0].AsNumber;
                impl.autoSelectOptionNumber  = (int)parameters[1].AsNumber;
            });

            dialogue.library.RegisterFunction("expect_line", 1, delegate(Value[] parameters)
            {
                impl.expectedNextLine = parameters[0].AsString;
            });

            dialogue.library.RegisterFunction("expect_command", 1, delegate(Value[] parameters)
            {
                impl.expectedNextCommand = parameters[0].AsString;
            });

            // If debugging is enabled, log debug messages; otherwise, ignore them
            if (options.showDebuggingInfo)
            {
                dialogue.LogDebugMessage = delegate(string message)
                {
                    Note(message);
                };
            }
            else
            {
                dialogue.LogDebugMessage = delegate(string message) { };
            }

            dialogue.LogErrorMessage = delegate(string message)
            {
                Warn("Yarn Error: " + message);
            };

            foreach (var file in options.files)
            {
                try
                {
                    dialogue.LoadFile(file, false, false, options.onlyConsiderNode);
                }
                catch (Yarn.ParseException e)
                {
                    Warn(e.Message);
                }
            }

            // Load string table
            if (options.stringTable != null)
            {
                var parsedTable = new Dictionary <string, string>();

                using (var reader = new System.IO.StreamReader(options.stringTable))
                {
                    using (var csvReader = new CsvReader(reader))
                    {
                        if (csvReader.ReadHeader() == false)
                        {
                            Error(string.Format(CultureInfo.CurrentCulture, "{0} is not a valid string table", options.stringTable));
                        }

                        foreach (var row in csvReader.GetRecords <LocalisedLine>())
                        {
                            parsedTable[row.LineCode] = row.LineText;
                        }
                    }
                }

                dialogue.AddStringTable(parsedTable);
            }

            return(dialogue);
        }
コード例 #5
0
ファイル: Main.cs プロジェクト: thesecretlab/YarnSpinner
        public static void Main(string[] args)
        {
            if (args.Length == 0) {
                ShowHelpAndExit ();
            }

            bool showTokens = false;
            bool showParseTree = false;
            bool waitForLines = false;
            string onlyConsiderNode = null;
            bool showDebugging = false;
            int runTimes = 1;
            bool compileToBytecodeOnly = false;
            bool verifyOnly = false;
            bool autoSelectFirstOption = false;
            bool analyseOnly = false;

            var inputFiles = new List<string> ();
            string startNode = Dialogue.DEFAULT_START;

            string[] allowedExtensions = {".node", ".json" };

            var defaultVariables = new Dictionary<string,float> ();

            foreach (var arg in args) {

                // Handle 'start' parameter
                if (arg.IndexOf("-s=") != -1) {
                    var startArray = arg.Split (new char[]{ '=' });
                    if (startArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        startNode = startArray [1];
                        continue;
                    }
                }

                // Handle variable input
                if (arg.IndexOf("-v") != -1) {
                    var variable = arg.Substring (2);
                    var variableArray = variable.Split (new char[]{ '=' });
                    if (variableArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        var varName = "$" + variableArray [0];
                        var varValue = float.Parse (variableArray [1]);
                        defaultVariables [varName] = varValue;
                        continue;
                    }

                }

                // Handle 'only this node' parameter
                if (arg.IndexOf("-o=") != -1) {
                    var startArray = arg.Split (new char[]{ '=' });
                    if (startArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        onlyConsiderNode = startArray [1];
                        continue;
                    }
                }

                // Handle 'run times' parameter
                if (arg.IndexOf("-r=") != -1) {
                    var argArray = arg.Split ('=');
                    if (argArray.Length != 2) {
                        ShowHelpAndExit ();
                    } else {
                        runTimes = int.Parse (argArray [1]);
                        continue;
                    }

                }

                switch (arg) {
                case "-V":
                    verifyOnly = true;
                    break;
                case "-t":
                    showTokens = true;
                    showDebugging = true;
                    break;
                case "-p":
                    showParseTree = true;
                    showDebugging = true;
                    break;
                case "-w":
                    waitForLines = true;
                    break;
                case "-d":
                    showDebugging = true;
                    break;
                case "-c":
                    compileToBytecodeOnly = true;
                    break;
                case "-1":
                    autoSelectFirstOption = true;
                    break;
                case "-h":
                    ShowHelpAndExit ();
                    break;
                case "-a":
                    analyseOnly = true;
                    break;
                default:

                    // only allow one file
                    if (inputFiles.Count > 0) {
                        Console.Error.WriteLine ("Error: Too many files specified.");
                        Environment.Exit (1);
                    }

                    var extension = System.IO.Path.GetExtension (arg);
                    if (Array.IndexOf(allowedExtensions, extension) != -1) {
                        inputFiles.Add (arg);
                    }
                    break;
                }
            }

            if (inputFiles.Count == 0) {
                Console.Error.WriteLine ("Error: No files specified.");
                Environment.Exit (1);
            }

            // Create the object that handles callbacks
            var impl = new ConsoleRunnerImplementation (waitForLines:waitForLines);

            // load the default variables we got on the command line
            foreach (var variable in defaultVariables) {

                impl.SetNumber (variable.Key, variable.Value);
            }

            // Load nodes
            var dialogue = new Dialogue(impl);

            // Add some methods for testing
            dialogue.library.RegisterFunction ("add_three_operands", 3, delegate(Value[] parameters) {
                return parameters[0]+parameters[1]+parameters[2];
            });

            dialogue.library.RegisterFunction ("last_value", -1, delegate(Value[] parameters) {
                // return the last value
                return parameters[parameters.Length-1];
            });

            dialogue.library.RegisterFunction ("is_even", 1, delegate(Value[] parameters) {
                return (int)parameters[0].AsNumber % 2 == 0;
            });

            // Register the "assert" function, which stops execution if its parameter evaluates to false
            dialogue.library.RegisterFunction ("assert", -1, delegate(Value[] parameters) {
                if (parameters[0].AsBool == false) {

                    // TODO: Include file, node and line number
                    if( parameters.Length > 1 && parameters[1].AsBool ) {
                        dialogue.LogErrorMessage ("ASSERTION FAILED: " + parameters[1].AsString);
                    } else {
                        dialogue.LogErrorMessage ("ASSERTION FAILED");
                    }
                    Environment.Exit(1);
                }
            });

            // Register a function to let test scripts register how many
            // options they expect to send
            dialogue.library.RegisterFunction ("prepare_for_options", 2, delegate(Value[] parameters) {
                impl.numberOfExpectedOptions = (int)parameters [0].AsNumber;
                impl.autoSelectOptionNumber = (int)parameters[1].AsNumber;
            });

            dialogue.library.RegisterFunction ("expect_line", 1, delegate(Value[] parameters) {
                impl.expectedNextLine = parameters[0].AsString;
            });

            dialogue.library.RegisterFunction ("expect_command", 1, delegate(Value[] parameters) {
                impl.expectedNextCommand = parameters[0].AsString;
            });

            if (autoSelectFirstOption == true) {
                impl.autoSelectFirstOption = true;
            }

            // If debugging is enabled, log debug messages; otherwise, ignore them
            if (showDebugging) {
                dialogue.LogDebugMessage = delegate(string message) {
                    Console.WriteLine ("Debug: " + message);
                };
            } else {
                dialogue.LogDebugMessage = delegate(string message) {};
            }

            dialogue.LogErrorMessage = delegate(string message) {
                Console.WriteLine ("ERROR: " + message);
            };

            if (verifyOnly) {
                try {
                    dialogue.LoadFile (inputFiles [0],showTokens, showParseTree, onlyConsiderNode);
                } catch (Exception e) {
                    Console.WriteLine ("Error: " + e.Message);
                }
                return;
            }

            dialogue.LoadFile (inputFiles [0],showTokens, showParseTree, onlyConsiderNode);

            if (compileToBytecodeOnly) {
                var result = dialogue.GetByteCode ();
                Console.WriteLine (result);
                return;
            }

            if (analyseOnly) {

                var context = new Yarn.Analysis.Context ();

                dialogue.Analyse (context);

                foreach (var diagnosis in context.FinishAnalysis()) {
                    Console.WriteLine (diagnosis.ToString(showSeverity:true));
                }
                return;
            }

            // Only run the program when we're not emitting debug output of some kind
            var runProgram =
                showTokens == false &&
                showParseTree == false &&
                compileToBytecodeOnly == false &&
                analyseOnly == false;

            if (runProgram) {
                // Run the conversation

                for (int run = 0; run < runTimes; run++) {
                    foreach (var step in dialogue.Run (startNode)) {

                        // It can be one of three types: a line to show, options
                        // to present to the user, or an internal command to run

                        if (step is Dialogue.LineResult) {
                            var lineResult = step as Dialogue.LineResult;
                            impl.RunLine (lineResult.line);
                        } else if (step is Dialogue.OptionSetResult) {
                            var optionsResult = step as Dialogue.OptionSetResult;
                            impl.RunOptions (optionsResult.options, optionsResult.setSelectedOptionDelegate);
                        } else if (step is Dialogue.CommandResult) {
                            var commandResult = step as Dialogue.CommandResult;
                            impl.RunCommand (commandResult.command.text);
                        }
                    }
                    impl.DialogueComplete ();
                }

            }
        }