Inheritance: FlowBase
 public void PostParse(Parsed.Story parsedStory)
 {
     foreach (var plugin in _plugins)
     {
         plugin.PostParse(parsedStory);
     }
 }
 public void PostExport(Parsed.Story parsedStory, Runtime.Story runtimeStory)
 {
     foreach (var plugin in _plugins)
     {
         plugin.PostExport(parsedStory, runtimeStory);
     }
 }
Example #3
0
        public override void ResolveReferences (Story context)
        {
            base.ResolveReferences (context);

            VariableAssignment varDecl = null;
            if (this.isNewTemporaryDeclaration && story.variableDeclarations.TryGetValue(variableName, out varDecl) ) {
                if (varDecl.isGlobalDeclaration) {
                    Error ("global variable '" + variableName + "' already exists with the same name (declared on " + varDecl.debugMetadata + ")");
                    return;
                }
            }

            if (this.isGlobalDeclaration) {
                var variableReference = expression as VariableReference;
                if (variableReference && !variableReference.isConstantReference) {
                    Error ("global variable assignments cannot refer to other variables, only literal values and constants");
                }       
            }

            if (IsReservedKeyword (variableName)) {
                Error ("cannot use '" + variableName + "' as a variable since it's a reserved ink keyword");
                return;
            }

            if (!this.isNewTemporaryDeclaration) {
                if (!context.ResolveVariableWithName (this.variableName, fromNode:this).found) {
                    if (story.constants.ContainsKey (variableName)) {
                        Error ("Can't re-assign to a constant (do you need to use VAR when declaring '" + this.variableName + "'?)", this);
                    } else {
                        Error ("Variable could not be found to assign to: '" + this.variableName + "'", this);
                    }

                }
            }
        }
Example #4
0
        protected object IncludeStatement()
        {
            Whitespace();

            if (ParseString("INCLUDE") == null)
            {
                return(null);
            }

            Whitespace();

            var filename = (string)Expect(() => ParseUntilCharactersFromString("\n\r"), "filename for include statement");

            filename = filename.TrimEnd(' ', '\t');

            var fullFilename = filename;

            if (_rootDirectory != null)
            {
                fullFilename = System.IO.Path.Combine(_rootDirectory, filename);
            }

            Parsed.Story includedStory  = null;
            string       includedString = null;

            try {
                includedString = System.IO.File.ReadAllText(fullFilename);
            }
            catch {
                string message = "Failed to load: " + filename;
                if (_rootDirectory != null)
                {
                    message += "' (root directory is " + _rootDirectory + "). File not found perhaps?";
                }
                Error(message);
            }


            if (includedString != null)
            {
                InkParser parser = new InkParser(includedString, filename, _rootDirectory);
                includedStory = parser.Parse();

                if (includedStory == null)
                {
                    // This error should never happen: if the includedStory isn't
                    // returned, then it should've been due to some error that
                    // has already been reported, so this is a last resort.
                    if (!parser.hadError)
                    {
                        Error("Failed to parse included file '" + filename);
                    }
                }
            }

            // Return valid IncludedFile object even when story failed to parse and we have a null story:
            // we don't want to attempt to re-parse the include line as something else
            return(new IncludedFile(includedStory));
        }
Example #5
0
        public CommandLinePlayer(Story story, bool autoPlay = false, Parsed.Story parsedStory = null, bool keepOpenAfterStoryFinish = false)
        {
            this.story       = story;
            this.autoPlay    = autoPlay;
            this.parsedStory = parsedStory;
            this.keepOpenAfterStoryFinish = keepOpenAfterStoryFinish;

            _debugSourceRanges = new List <DebugSourceRange> ();
        }
Example #6
0
        public override void ResolveReferences (Story context)
        {
            var pathToReJoin = _reJoinTarget.path;

            foreach (var branch in branches) {
                branch.returnDivert.targetPath = pathToReJoin;
            }

            base.ResolveReferences (context);
        }
Example #7
0
        protected object IncludeStatement()
        {
            Whitespace();

            if (ParseString("INCLUDE") == null)
            {
                return(null);
            }

            Whitespace();

            var filename = (string)Expect(() => ParseUntilCharactersFromString("\n\r"), "filename for include statement");

            filename = filename.TrimEnd(' ', '\t');

            // Working directory should already have been set up relative to the root ink file.
            var workingDirectory = Directory.GetCurrentDirectory();
            var fullFilename     = System.IO.Path.Combine(workingDirectory, filename);

            if (FilenameIsAlreadyOpen(fullFilename))
            {
                Error("Recursive INCLUDE detected: '" + fullFilename + "' is already open.");
                ParseUntilCharactersFromString("\r\n");
                return(new IncludedFile(null));
            }
            else
            {
                AddOpenFilename(fullFilename);
            }

            Parsed.Story includedStory  = null;
            string       includedString = null;

            try {
                includedString = File.ReadAllText(fullFilename);
            }
            catch {
                Error("Failed to load: '" + filename + "' (relative to directory: " + workingDirectory + ")");
            }


            if (includedString != null)
            {
                InkParser parser = new InkParser(includedString, filename, _externalErrorHandler, _rootParser);
                includedStory = parser.Parse();
            }

            RemoveOpenFilename(fullFilename);

            // Return valid IncludedFile object even if there were errors when parsing.
            // We don't want to attempt to re-parse the include line as something else,
            // and we want to include the bits that *are* valid, so we don't generate
            // more errors than necessary.
            return(new IncludedFile(includedStory));
        }
Example #8
0
        public Parsed.Story PostParse(Parsed.Story parsedStory)
        {
            object[] args = new object[] { parsedStory };

            foreach (var plugin in _plugins)
            {
                typeof(IPlugin).InvokeMember("PostParse", BindingFlags.InvokeMethod, null, plugin, args);
            }

            return((Parsed.Story)args[0]);
        }
Example #9
0
        public Runtime.Story PostExport(Parsed.Story parsedStory, Runtime.Story runtimeStory)
        {
            object[] args = new object[] { parsedStory, runtimeStory };

            foreach (var plugin in _plugins)
            {
                typeof(IPlugin).InvokeMember("PostExport", BindingFlags.InvokeMethod, null, plugin, args);
            }

            return((Runtime.Story)args[1]);
        }
Example #10
0
        public override void ResolveReferences (Story context)
        {
            base.ResolveReferences (context);

            // Work is already done if it's a constant reference
            if (isConstantReference) {
                return;
            }
                
            // Is it a read count?
            var parsedPath = new Path (path);
            Parsed.Object targetForCount = parsedPath.ResolveFromContext (this);
            if (targetForCount) {

                targetForCount.containerForCounting.visitsShouldBeCounted = true;

                _runtimeVarRef.pathForCount = targetForCount.runtimePath;
                _runtimeVarRef.name = null;

                // Check for very specific writer error: getting read count and
                // printing it as content rather than as a piece of logic
                // e.g. Writing {myFunc} instead of {myFunc()}
                var targetFlow = targetForCount as FlowBase;
                if (targetFlow && targetFlow.isFunction) {

                    // Is parent context content rather than logic?
                    if ( parent is Weave || parent is ContentList || parent is FlowBase) {
                        Warning ("'" + targetFlow.name + "' being used as read count rather than being called as function. Perhaps you intended to write " + targetFlow.name + "()");
                    }
                }

                return;
            } 

            // Definitely a read count, but wasn't found?
            else if (path.Count > 1) {
                Error ("Could not find target for read count: " + parsedPath);
                return;
            }

            if (!context.ResolveVariableWithName (this.name, fromNode: this).found) {
                Error("Unresolved variable: "+this.ToString(), this);
            }
        }
Example #11
0
File: Knot.cs Project: inkle/ink
        public override void ResolveReferences (Story context)
        {
            base.ResolveReferences (context);

            var parentStory = this.story;

            // Enforce rule that stitches must not have the same
            // name as any knots that exist in the story
            foreach (var stitchNamePair in subFlowsByName) {
                var stitchName = stitchNamePair.Key;

                var knotWithStitchName = parentStory.ContentWithNameAtLevel (stitchName, FlowLevel.Knot, false);
                if (knotWithStitchName) {
                    var stitch = stitchNamePair.Value;
                    var errorMsg = string.Format ("Stitch '{0}' has the same name as a knot (on {1})", stitch.name, knotWithStitchName.debugMetadata);
                    Error(errorMsg, stitch);
                }
            }
        }
Example #12
0
        public void PostExport(Ink.Parsed.Story parsedStory, Ink.Runtime.Story runtimeStory)
        {
            var choiceJsonArray = new JArray();

            var allChoices = parsedStory.FindAll <Choice>();

            foreach (Ink.Parsed.Choice choice in allChoices)
            {
                var sb = new StringBuilder();

                if (choice.startContent != null)
                {
                    sb.Append(choice.startContent.ToString());
                }

                if (choice.choiceOnlyContent)
                {
                    sb.Append(choice.choiceOnlyContent.ToString());
                }

                // Note that this choice text is an approximation since
                // it can be dynamically generated at runtime. We are therefore
                // making the assumption that the startContent and choiceOnlyContent
                // lists contain only string value content.
                var choiceTextApproximation = sb.ToString();
                var filename = choice.debugMetadata.fileName;

                var jsonObj = new JObject();
                jsonObj ["filename"]   = filename;
                jsonObj ["choiceText"] = choiceTextApproximation.ToString();

                choiceJsonArray.Add(jsonObj);
            }

            var jsonString = choiceJsonArray.ToString();

            File.WriteAllText("choiceList.json", jsonString, System.Text.Encoding.UTF8);
        }
Example #13
0
File: Divert.cs Project: inkle/ink
        void CheckExternalArgumentValidity(Story context)
        {
            string externalName = target.firstComponent;
            ExternalDeclaration external = null;
            var found = context.externals.TryGetValue(externalName, out external);
            System.Diagnostics.Debug.Assert (found, "external not found");

            int externalArgCount = external.argumentNames.Count;
            int ownArgCount = 0;
            if (arguments != null) {
                ownArgCount = arguments.Count;
            }

            if (ownArgCount != externalArgCount) {
                Error ("incorrect number of arguments sent to external function '" + externalName + "'. Expected " + externalArgCount + " but got " + ownArgCount);
            }
        }
Example #14
0
File: Weave.cs Project: tomkail/ink
        public override void ResolveReferences(Story context)
        {
            base.ResolveReferences (context);

            foreach(var gatherPoint in gatherPointsToResolve) {
                gatherPoint.divert.targetPath = gatherPoint.targetRuntimeObj.path;
            }

            CheckForWeavePointNamingCollisions ();
        }
Example #15
0
 public override void ResolveReferences (Story context)
 {
     if (!context.ResolveVariableWithName (varName, fromNode:this).found) {
         Error ("variable for "+incrementDecrementWord+" could not be found: '"+varName+"' after searching: "+this.descriptionOfScope);
     }
 }
Example #16
0
File: Divert.cs Project: inkle/ink
        public override void ResolveReferences(Story context)
		{
            if (isToGather || isEnd || isDone) {
                return;
            }

            if (targetContent) {
                runtimeDivert.targetPath = targetContent.runtimePath;
            }

            // Resolve children (the arguments)
            base.ResolveReferences (context);

            // May be null if it's a built in function (e.g. TURNS_SINCE)
            // or if it's a variable target.
            var targetFlow = targetContent as FlowBase;
            if (targetFlow) {
                if (!targetFlow.isFunction && this.isFunctionCall) {
                    base.Error (targetFlow.name + " hasn't been marked as a function, but it's being called as one. Do you need to delcare the knot as '== function " + targetFlow.name + " =='?");
                } else if (targetFlow.isFunction && !this.isFunctionCall && !(this.parent is DivertTarget)) {
                    base.Error (targetFlow.name + " can't be diverted to. It can only be called as a function since it's been marked as such: '" + targetFlow.name + "(...)'");
                }
            } 

            // Check validity of target content
            bool targetWasFound = targetContent != null;
            bool isBuiltIn = false;
            bool isExternal = false;

            if (target.numberOfComponents == 1 ) {

                // BuiltIn means TURNS_SINCE, CHOICE_COUNT, RANDOM or SEED_RANDOM
                isBuiltIn = FunctionCall.IsBuiltIn (target.firstComponent);

                // Client-bound function?
                isExternal = context.IsExternal (target.firstComponent);

                if (isBuiltIn || isExternal) {
                    if (!isFunctionCall) {
                        base.Error (target.firstComponent + " must be called as a function: ~ " + target.firstComponent + "()");
                    }
                    if (isExternal) {
                        runtimeDivert.isExternal = true;
                        if( arguments != null )
                            runtimeDivert.externalArgs = arguments.Count;
                        runtimeDivert.pushesToStack = false;
                        runtimeDivert.targetPath = new Runtime.Path (this.target.firstComponent);
                        CheckExternalArgumentValidity (context);
                    }
                    return;
                }
            }

            // Variable target?
            if (runtimeDivert.variableDivertName != null) {
                return;
            }
                  
            if( !targetWasFound && !isBuiltIn && !isExternal )
                Error ("target not found: '" + target + "'");
		}
Example #17
0
        //public override Runtime.Object GenerateRuntimeObject ()
        //{
        //    var initialValues = new Runtime.InkList ();
        //    foreach (var itemDef in itemDefinitions) {
        //        if (itemDef.inInitialList) {
        //            var item = new Runtime.InkListItem (this.name, itemDef.name);
        //            initialValues [item] = itemDef.seriesValue;
        //        }
        //    }

        //    // Set origin name, so
        //    initialValues.SetInitialOriginName (name);

        //    return new Runtime.ListValue (initialValues);
        //}

        public override void ResolveReferences(Story context)
        {
            base.ResolveReferences(context);

            context.CheckForNamingCollisions(this, name, Story.SymbolType.List);
        }
Example #18
0
        public override void ResolveReferences(Story context)
        {
            base.ResolveReferences (context);

            foreach (var toResolve in _sequenceDivertsToResove) {
                toResolve.divert.targetPath = toResolve.targetContent.path;
            }
        }
Example #19
0
        CommandLineTool(string[] args)
        {
            if (ProcessArguments(args) == false)
            {
                ExitWithUsageInstructions();
            }

            if (opts.testMode)
            {
                opts.inputFile = "test.ink";
            }

            if (opts.inputFile == null)
            {
                ExitWithUsageInstructions();
            }

            if (opts.outputFile == null)
            {
                opts.outputFile = Path.ChangeExtension(opts.inputFile, ".ink.json");
            }

            string inputString   = null;
            string rootDirectory = System.IO.Directory.GetCurrentDirectory();

            if (opts.workingDirectory != null)
            {
                rootDirectory = Path.GetFullPath(opts.workingDirectory);
            }

            if (opts.stressTest)
            {
                StressTestContentGenerator stressTestContent = null;
                TimeOperation("Generating test content", () => {
                    stressTestContent = new StressTestContentGenerator(100);
                });

                Console.WriteLine("Generated ~{0}k of test ink", stressTestContent.sizeInKiloChars);

                inputString = stressTestContent.content;
            }
            else
            {
                try {
                    string fullFilename = opts.inputFile;
                    if (!Path.IsPathRooted(fullFilename))
                    {
                        fullFilename = Path.Combine(rootDirectory, fullFilename);
                    }

                    inputString = File.ReadAllText(fullFilename);
                }
                catch {
                    Console.WriteLine("Could not open file '" + opts.inputFile + "'");
                    Environment.Exit(ExitCodeError);
                }
            }

            InkParser parser = null;

            Parsed.Story  parsedStory = null;
            Runtime.Story story       = null;
            errors         = new List <string> ();
            warnings       = new List <string> ();
            authorMessages = new List <string> ();
            var pluginManager = new PluginManager(pluginNames);

            var inputIsJson = opts.inputFile.EndsWith(".json");

            // Loading a normal ink file (as opposed to an already compiled json file)
            if (!inputIsJson)
            {
                TimeOperation("Creating parser", () => {
                    parser = new InkParser(inputString, opts.inputFile, rootDirectory, OnError);
                });

                TimeOperation("Parsing", () => {
                    parsedStory = parser.Parse();
                });

                TimeOperation("PostParsePlugins", () => {
                    pluginManager.PostParse(parsedStory);
                });

                if (parsedStory != null)
                {
                    if (opts.countAllVisits)
                    {
                        parsedStory.countAllVisits = true;
                    }

                    TimeOperation("Exporting runtime", () => {
                        story = parsedStory.ExportRuntime(OnError);
                    });

                    TimeOperation("PostParsePlugins", () => {
                        pluginManager.PostExport(parsedStory, story);
                    });
                }
            }

            // Opening up a compiled json file for playing
            else
            {
                story = Runtime.Story.CreateWithJson(inputString);

                // No purpose for loading an already compiled file other than to play it
                opts.playMode = true;
            }

            PrintMessages(authorMessages, ConsoleColor.Green);
            PrintMessages(warnings, ConsoleColor.Blue);
            PrintMessages(errors, ConsoleColor.Red);

            if (story == null)
            {
                Environment.Exit(ExitCodeError);
            }

            // JSON round trip testing
//            if (opts.testMode) {
//                var jsonStr = story.ToJsonString (indented:true);
//                Console.WriteLine (jsonStr);
//
//                Console.WriteLine ("---------------------------------------------------");
//
//                var reloadedStory = Runtime.Story.CreateWithJson (jsonStr);
//                var newJsonStr = reloadedStory.ToJsonString (indented: true);
//                Console.WriteLine (newJsonStr);
//
//                story = reloadedStory;
//            }

            // Play mode
            // Test mode may use "-tp" in commmand line args to specify that
            // the test script is also played
            if (opts.playMode)
            {
                // Always allow ink external fallbacks
                story.allowExternalFunctionFallbacks = true;

                var player = new CommandLinePlayer(story, false, parsedStory);
                player.Begin();
            }

            // Compile mode
            else
            {
                var jsonStr = story.ToJsonString(opts.indentedJson);

                try {
                    File.WriteAllText(opts.outputFile, jsonStr, System.Text.Encoding.UTF8);
                } catch {
                    Console.WriteLine("Could write to output file '" + opts.outputFile + "'");
                    Environment.Exit(ExitCodeError);
                }
            }
        }
Example #20
0
 public void PostParse(Ink.Parsed.Story parsedStory)
 {
     // Nothing
 }
Example #21
0
        protected object IncludeStatement()
        {
            Whitespace();

            if (ParseString("INCLUDE") == null)
            {
                return(null);
            }

            Whitespace();

            var filename = (string)Expect(() => ParseUntilCharactersFromString("\n\r"), "filename for include statement");

            filename = filename.TrimEnd(' ', '\t');

            // Working directory should already have been set up relative to the root ink file.
            var workingDirectory = Directory.GetCurrentDirectory();
            var fullFilename     = System.IO.Path.Combine(workingDirectory, filename);

            if (FilenameIsAlreadyOpen(fullFilename))
            {
                Error("Recursive INCLUDE detected: '" + fullFilename + "' is already open.");
                ParseUntilCharactersFromString("\r\n");
                return(new IncludedFile(null));
            }
            else
            {
                AddOpenFilename(fullFilename);
            }

            Parsed.Story includedStory  = null;
            string       includedString = null;

            try {
                includedString = File.ReadAllText(fullFilename);
            }
            catch {
                Error("Failed to load: '" + filename + "' (relative to directory: " + workingDirectory + ")");
            }


            if (includedString != null)
            {
                InkParser parser = new InkParser(includedString, filename, _externalErrorHandler, _rootParser);
                includedStory = parser.Parse();

                if (includedStory == null)
                {
                    // This error should never happen: if the includedStory isn't
                    // returned, then it should've been due to some error that
                    // has already been reported, so this is a last resort.
                    if (!parser.hadError)
                    {
                        Error("Failed to parse included file '" + filename);
                    }
                }
            }

            RemoveOpenFilename(fullFilename);

            // Return valid IncludedFile object even when story failed to parse and we have a null story:
            // we don't want to attempt to re-parse the include line as something else
            return(new IncludedFile(includedStory));
        }
Example #22
0
        public override void ResolveReferences(Story context)
        {
            _conditionalDivert.targetPath = _contentContainer.path;

            base.ResolveReferences (context);
        }
Example #23
0
File: Divert.cs Project: nyyjen/ink
        public override void ResolveReferences(Story context)
        {
            if (isToGather || isEnd || isDone)
            {
                return;
            }

            if (targetContent)
            {
                runtimeDivert.targetPath = targetContent.runtimePath;
            }

            // Resolve children (the arguments)
            base.ResolveReferences(context);

            // May be null if it's a built in function (e.g. TURNS_SINCE)
            // or if it's a variable target.
            var targetFlow = targetContent as FlowBase;

            if (targetFlow)
            {
                if (!targetFlow.isFunction && this.isFunctionCall)
                {
                    base.Error(targetFlow.name + " hasn't been marked as a function, but it's being called as one. Do you need to delcare the knot as '== function " + targetFlow.name + " =='?");
                }
                else if (targetFlow.isFunction && !this.isFunctionCall && !(this.parent is DivertTarget))
                {
                    base.Error(targetFlow.name + " can't be diverted to. It can only be called as a function since it's been marked as such: '" + targetFlow.name + "(...)'");
                }
            }

            // Check validity of target content
            bool targetWasFound = targetContent != null;
            bool isBuiltIn      = false;
            bool isExternal     = false;

            if (target.numberOfComponents == 1)
            {
                // BuiltIn means TURNS_SINCE or CHOICE_COUNT
                isBuiltIn = FunctionCall.IsValidName(target.firstComponent);

                // Client-bound function?
                isExternal = context.IsExternal(target.firstComponent);

                if (isBuiltIn || isExternal)
                {
                    if (!isFunctionCall)
                    {
                        base.Error(target.firstComponent + " must be called as a function: ~ " + target.firstComponent + "()");
                    }
                    if (isExternal)
                    {
                        runtimeDivert.isExternal = true;
                        if (arguments != null)
                        {
                            runtimeDivert.externalArgs = arguments.Count;
                        }
                        runtimeDivert.pushesToStack = false;
                        runtimeDivert.targetPath    = new Runtime.Path(this.target.firstComponent);
                        CheckExternalArgumentValidity(context);
                    }
                    return;
                }
            }

            // Variable target?
            if (runtimeDivert.variableDivertName != null)
            {
                return;
            }

            if (!targetWasFound && !isBuiltIn && !isExternal)
            {
                Error("target not found: '" + target + "'");
            }
        }
Example #24
0
        public override void ResolveReferences(Story context)
        {
            _conditionalDivert.targetPath = _contentContainer.path;

            base.ResolveReferences(context);
        }
Example #25
0
        public override void ResolveReferences(Story context)
        {
            // Weave style choice - target own content container
            if (_innerContentContainer) {
                _runtimeChoice.pathOnChoice = _innerContentContainer.path;

                if (onceOnly)
                    _innerContentContainer.visitsShouldBeCounted = true;
            }

            if( _divertToStartContentOuter )
                _divertToStartContentOuter.targetPath = _startContentRuntimeContainer.path;

            if( _divertToStartContentInner )
                _divertToStartContentInner.targetPath = _startContentRuntimeContainer.path;

            base.ResolveReferences (context);
        }
Example #26
0
        CommandLineTool(string[] args)
        {
            // Set console's output encoding to UTF-8
            Console.OutputEncoding = System.Text.Encoding.UTF8;

            if (ProcessArguments(args) == false)
            {
                ExitWithUsageInstructions();
            }

            if (opts.inputFile == null)
            {
                ExitWithUsageInstructions();
            }

            string inputString      = null;
            string workingDirectory = Directory.GetCurrentDirectory();

            if (opts.outputFile == null)
            {
                opts.outputFile = Path.ChangeExtension(opts.inputFile, ".ink.json");
            }

            if (!Path.IsPathRooted(opts.outputFile))
            {
                opts.outputFile = Path.Combine(workingDirectory, opts.outputFile);
            }

            try {
                string fullFilename = opts.inputFile;
                if (!Path.IsPathRooted(fullFilename))
                {
                    fullFilename = Path.Combine(workingDirectory, fullFilename);
                }

                // Make the working directory the directory for the root ink file,
                // so that relative paths for INCLUDE files are correct.
                workingDirectory = Path.GetDirectoryName(fullFilename);
                Directory.SetCurrentDirectory(workingDirectory);

                // Now make the input file relative to the working directory,
                // but just getting the file's actual name.
                opts.inputFile = Path.GetFileName(fullFilename);

                inputString = File.ReadAllText(opts.inputFile);
            }
            catch {
                Console.WriteLine("Could not open file '" + opts.inputFile + "'");
                Environment.Exit(ExitCodeError);
            }

            var inputIsJson = opts.inputFile.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase);

            if (inputIsJson && opts.stats)
            {
                Console.WriteLine("Cannot show stats for .json, only for .ink");
                Environment.Exit(ExitCodeError);
            }

            Parsed.Story  parsedStory = null;
            Runtime.Story story       = null;
            Compiler      compiler    = null;

            // Loading a normal ink file (as opposed to an already compiled json file)
            if (!inputIsJson)
            {
                compiler = new Compiler(inputString, new Compiler.Options {
                    sourceFilename = opts.inputFile,
                    pluginNames    = pluginNames,
                    countAllVisits = opts.countAllVisits,
                    errorHandler   = OnError,
                    fileHandler    = this
                });

                // Only want stats, don't need to code-gen
                if (opts.stats)
                {
                    parsedStory = compiler.Parse();

                    // Print any errors
                    PrintAllMessages();

                    // Generate stats, then print as JSON
                    var stats = Ink.Stats.Generate(compiler.parsedStory);

                    var writer = new Runtime.SimpleJson.Writer();

                    writer.WriteObjectStart();
                    writer.WriteProperty("words", stats.words);
                    writer.WriteProperty("knots", stats.knots);
                    writer.WriteProperty("stitches", stats.stitches);
                    writer.WriteProperty("functions", stats.functions);
                    writer.WriteProperty("choices", stats.choices);
                    writer.WriteProperty("gathers", stats.gathers);
                    writer.WriteProperty("diverts", stats.diverts);
                    writer.WriteObjectEnd();

                    Console.WriteLine(writer.ToString());

                    return;
                }

                // Full compile
                else
                {
                    story = compiler.Compile();
                }
            }

            // Opening up a compiled json file for playing
            else
            {
                story = new Runtime.Story(inputString);

                // No purpose for loading an already compiled file other than to play it
                opts.playMode = true;
            }

            PrintAllMessages();

            if (story == null || _errors.Count > 0)
            {
                Environment.Exit(ExitCodeError);
            }

            // Play mode
            if (opts.playMode)
            {
                _playing = true;

                // Always allow ink external fallbacks
                story.allowExternalFunctionFallbacks = true;

                var player = new CommandLinePlayer(story, false, compiler, opts.keepOpenAfterStoryFinish);

                //Capture a CTRL+C key combo so we can restore the console's foreground color back to normal when exiting
                Console.CancelKeyPress += OnExit;

                try {
                    player.Begin();
                } catch (Runtime.StoryException e) {
                    if (e.Message.Contains("Missing function binding"))
                    {
                        OnError(e.Message, ErrorType.Error);
                        PrintAllMessages();
                    }
                    else
                    {
                        throw e;
                    }
                } catch (System.Exception e) {
                    string storyPath = "<END>";
                    var    path      = story.state.currentPathString;
                    if (path != null)
                    {
                        storyPath = path.ToString();
                    }
                    throw new System.Exception(e.Message + " (Internal story path: " + storyPath + ")", e);
                }
            }

            // Compile mode
            else
            {
                var jsonStr = story.ToJson();

                try {
                    File.WriteAllText(opts.outputFile, jsonStr, System.Text.Encoding.UTF8);
                } catch {
                    Console.WriteLine("Could not write to output file '" + opts.outputFile + "'");
                    Environment.Exit(ExitCodeError);
                }
            }
        }
Example #27
0
 public CommandLinePlayer(Story story, bool autoPlay = false, Parsed.Story parsedStory = null)
 {
     this.story       = story;
     this.autoPlay    = autoPlay;
     this.parsedStory = parsedStory;
 }
Example #28
0
        CommandLineTool(string[] args)
        {
            // Set console's output encoding to UTF-8
            Console.OutputEncoding = System.Text.Encoding.UTF8;

            if (ProcessArguments(args) == false)
            {
                ExitWithUsageInstructions();
            }

            if (opts.testMode)
            {
                opts.inputFile = "test.ink";
            }

            if (opts.inputFile == null)
            {
                ExitWithUsageInstructions();
            }

            string inputString      = null;
            string workingDirectory = Directory.GetCurrentDirectory();

            if (opts.outputFile == null)
            {
                opts.outputFile = Path.ChangeExtension(opts.inputFile, ".ink.json");
            }

            if (!Path.IsPathRooted(opts.outputFile))
            {
                opts.outputFile = Path.Combine(workingDirectory, opts.outputFile);
            }

            if (opts.stressTest)
            {
                StressTestContentGenerator stressTestContent = null;
                TimeOperation("Generating test content", () => {
                    stressTestContent = new StressTestContentGenerator(100);
                });

                Console.WriteLine("Generated ~{0}k of test ink", stressTestContent.sizeInKiloChars);

                inputString = stressTestContent.content;
            }
            else
            {
                try {
                    string fullFilename = opts.inputFile;
                    if (!Path.IsPathRooted(fullFilename))
                    {
                        fullFilename = Path.Combine(workingDirectory, fullFilename);
                    }

                    // Make the working directory the directory for the root ink file,
                    // so that relative paths for INCLUDE files are correct.
                    workingDirectory = Path.GetDirectoryName(fullFilename);
                    Directory.SetCurrentDirectory(workingDirectory);

                    // Now make the input file relative to the working directory,
                    // but just getting the file's actual name.
                    opts.inputFile = Path.GetFileName(fullFilename);

                    inputString = File.ReadAllText(opts.inputFile);
                }
                catch {
                    Console.WriteLine("Could not open file '" + opts.inputFile + "'");
                    Environment.Exit(ExitCodeError);
                }
            }

            InkParser parser = null;

            Parsed.Story  parsedStory = null;
            Runtime.Story story       = null;
            errors         = new List <string> ();
            warnings       = new List <string> ();
            authorMessages = new List <string> ();
            var pluginManager = new PluginManager(pluginNames);

            var inputIsJson = opts.inputFile.EndsWith(".json");

            // Loading a normal ink file (as opposed to an already compiled json file)
            if (!inputIsJson)
            {
                TimeOperation("Creating parser", () => {
                    parser = new InkParser(inputString, opts.inputFile, OnError);
                });

                TimeOperation("Parsing", () => {
                    parsedStory = parser.Parse();
                });

                TimeOperation("PostParsePlugins", () => {
                    pluginManager.PostParse(parsedStory);
                });

                if (parsedStory != null && errors.Count == 0)
                {
                    if (opts.countAllVisits)
                    {
                        parsedStory.countAllVisits = true;
                    }

                    TimeOperation("Exporting runtime", () => {
                        story = parsedStory.ExportRuntime(OnError);
                    });

                    TimeOperation("PostParsePlugins", () => {
                        pluginManager.PostExport(parsedStory, story);
                    });
                }
            }

            // Opening up a compiled json file for playing
            else
            {
                story = new Runtime.Story(inputString);

                // No purpose for loading an already compiled file other than to play it
                opts.playMode = true;
            }

            PrintAllMessages();

            if (story == null || errors.Count > 0)
            {
                Environment.Exit(ExitCodeError);
            }

            // JSON round trip testing
            //if (opts.testMode) {
            //    var jsonStr = story.ToJsonString ();
            //    Console.WriteLine (jsonStr);

            //    Console.WriteLine ("---------------------------------------------------");

            //    var reloadedStory = new Runtime.Story (jsonStr);
            //    var newJsonStr = reloadedStory.ToJsonString ();
            //    Console.WriteLine (newJsonStr);

            //    story = reloadedStory;
            //}

            // Play mode
            // Test mode may use "-tp" in commmand line args to specify that
            // the test script is also played
            if (opts.playMode)
            {
                _playing = true;

                // Always allow ink external fallbacks
                story.allowExternalFunctionFallbacks = true;

                var player = new CommandLinePlayer(story, false, parsedStory, opts.keepOpenAfterStoryFinish);

                //Capture a CTRL+C key combo so we can restore the console's foreground color back to normal when exiting
                Console.CancelKeyPress += OnExit;

                try {
                    player.Begin();
                } catch (Runtime.StoryException e) {
                    if (e.Message.Contains("Missing function binding"))
                    {
                        OnError(e.Message, ErrorType.Error);
                        PrintAllMessages();
                    }
                    else
                    {
                        throw e;
                    }
                } catch (System.Exception e) {
                    string storyPath = "<END>";
                    var    path      = story.state.currentPath;
                    if (path != null)
                    {
                        storyPath = path.ToString();
                    }
                    throw new System.Exception(e.Message + " (Internal story path: " + storyPath + ")", e);
                }
            }

            // Compile mode
            else
            {
                var jsonStr = story.ToJsonString();

                try {
                    File.WriteAllText(opts.outputFile, jsonStr, System.Text.Encoding.UTF8);
                } catch {
                    Console.WriteLine("Could not write to output file '" + opts.outputFile + "'");
                    Environment.Exit(ExitCodeError);
                }
            }
        }
Example #29
0
        public override void ResolveReferences (Story context)
        {
            if (_finalLooseEndTarget) {
                var flowEndPath = _finalLooseEndTarget.path;
                foreach (var finalLooseEndDivert in _finalLooseEnds) {
                    finalLooseEndDivert.targetPath = flowEndPath;
                }
            }

            if (_startingSubFlowDivert) {
                _startingSubFlowDivert.targetPath = _startingSubFlowRuntime.path;
            }

            base.ResolveReferences(context);

            // Check validity of parameter names
            if (arguments != null) {
                foreach (var arg in arguments) {

                    // Don't allow reserved words for argument names
                    if (VariableAssignment.IsReservedKeyword (arg.name)) {
                        Error ("Argument '" + arg.name + "' is a reserved word, please choose another name");
                        continue;
                    }

                    // Does argument conflict with a knot/stitch/label?
                    var pathOfTheoreticalTarget = new Path (arg.name);
                    Parsed.Object target = pathOfTheoreticalTarget.ResolveFromContext (this);
                    if (target) {
                        Error ("Argument '" + arg.name + "' conflicts with a " + target.GetType().Name + " on " + target.debugMetadata + ", ");
                        continue;
                    }

                    // Does argument conflict with another variable name?
                    if (context.ResolveVariableWithName (arg.name, fromNode: this.parent).found) {
                        Error("Argument '"+ arg.name + "' conflicts with existing variable definition at higher scope.");
                        continue;
                    }
                }
            }
        }
Example #30
0
        public override void ResolveReferences (Story context)
        {
            base.ResolveReferences (context);

            if( _turnCountDivertTarget ) {
                var divert = _turnCountDivertTarget.divert;
                var attemptingTurnCountOfVariableTarget = divert.runtimeDivert.variableDivertName != null;

                if( attemptingTurnCountOfVariableTarget ) {
                    Error("When getting the TURNS_SINCE() of a variable target, remove the '->' - i.e. it should just be TURNS_SINCE("+divert.runtimeDivert.variableDivertName+")");
                    return;
                }

                var targetObject = divert.targetContent;
                if( targetObject == null ) {
                    if( !attemptingTurnCountOfVariableTarget ) {
                        Error("Failed to find target for TURNS_SINCE: '"+divert.target+"'");
                    }
                } else {
                    targetObject.containerForCounting.turnIndexShouldBeCounted = true;
                }
            }

            else if( _turnCountVariableReference ) {
                var runtimeVarRef = _turnCountVariableReference.runtimeVarRef;
                if( runtimeVarRef.pathForCount != null ) {
                    Error("Should be TURNS_SINCE(-> "+_turnCountVariableReference.name+"). Without the '->' it expects a variable target");
                }
            }
        }
Example #31
0
        //public override void GenerateIntoContainer (Runtime.Container container)
        //{
        //    Expression constantValue = null;

        //    // If it's a constant reference, just generate the literal expression value
        //    // It's okay to access the constants at code generation time, since the
        //    // first thing the ExportRuntime function does it search for all the constants
        //    // in the story hierarchy, so they're all available.
        //    if ( story.constants.TryGetValue (name, out constantValue) ) {
        //        constantValue.GenerateConstantIntoContainer (container);
        //        isConstantReference = true;
        //        return;
        //    }

        //    _runtimeVarRef = new Runtime.VariableReference (name);

        //    // List item reference?
        //    // Path might be to a list (listName.listItemName or just listItemName)
        //    if (path.Count == 1 || path.Count == 2) {
        //        string listItemName = null;
        //        string listName = null;

        //        if (path.Count == 1) listItemName = path [0];
        //        else {
        //            listName = path [0];
        //            listItemName = path [1];
        //        }

        //        var listItem = story.ResolveListItem (listName, listItemName, this);
        //        if (listItem) {
        //            isListItemReference = true;
        //        }
        //    }

        //    container.AddContent (_runtimeVarRef);
        //}

        public override void ResolveReferences(Story context)
        {
            base.ResolveReferences(context);

            // Work is already done if it's a constant or list item reference
            if (isConstantReference || isListItemReference)
            {
                return;
            }

            // Is it a read count?
            var parsedPath = new Path(path);

            Parsed.Object targetForCount = parsedPath.ResolveFromContext(this);
            if (targetForCount)
            {
                //targetForCount.containerForCounting.visitsShouldBeCounted = true;

                // If this is an argument to a function that wants a variable to be
                // passed by reference, then the Parsed.Divert will have generated a
                // Runtime.VariablePointerValue instead of allowing this object
                // to generate its RuntimeVariableReference. This only happens under
                // error condition since we shouldn't be passing a read count by
                // reference, but we don't want it to crash!
                //if (_runtimeVarRef == null) return;

                //_runtimeVarRef.pathForCount = targetForCount.runtimePath;
                //_runtimeVarRef.name = null;

                // Check for very specific writer error: getting read count and
                // printing it as content rather than as a piece of logic
                // e.g. Writing {myFunc} instead of {myFunc()}
                var targetFlow = targetForCount as FlowBase;
                if (targetFlow && targetFlow.isFunction)
                {
                    // Is parent context content rather than logic?
                    if (parent is Weave || parent is ContentList || parent is FlowBase)
                    {
                        Warning("'" + targetFlow.name + "' being used as read count rather than being called as function. Perhaps you intended to write " + targetFlow.name + "()");
                    }
                }

                return;
            }

            // Couldn't find this multi-part path at all, whether as a divert
            // target or as a list item reference.
            if (path.Count > 1)
            {
                var errorMsg = "Could not find target for read count: " + parsedPath;
                if (path.Count <= 2)
                {
                    errorMsg += ", or couldn't find list item with the name " + string.Join(",", path.ToArray());
                }
                Error(errorMsg);
                return;
            }

            if (!context.ResolveVariableWithName(this.name, fromNode: this).found)
            {
                Error("Unresolved variable: " + this.ToString(), this);
            }
        }