From dd85d4c4042e551e63bba2dc976ff530e3b65095 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Sat, 1 Oct 2016 19:08:48 +0200 Subject: [PATCH 1/8] Fix #32 --- engine/Story.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/Story.js b/engine/Story.js index c88c0c7d..8be1e56d 100644 --- a/engine/Story.js +++ b/engine/Story.js @@ -1120,7 +1120,7 @@ export class Story extends InkObject{ var errorPreamble = "ERROR: "; //misses a bit about metadata, which isn't implemented - throw new errorPreamble + message; + throw new StoryException(errorPreamble + message); } } } From b2726b9469cf3781cda408298d8ef15fcd629310 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Sat, 1 Oct 2016 19:30:45 +0200 Subject: [PATCH 2/8] Simple StringBuilder port, see #30 --- engine/StringBuilder.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 engine/StringBuilder.js diff --git a/engine/StringBuilder.js b/engine/StringBuilder.js new file mode 100644 index 00000000..c2644b42 --- /dev/null +++ b/engine/StringBuilder.js @@ -0,0 +1,26 @@ +export class StringBuilder{ + constructor(str){ + str = (typeof str !== 'undefined') ? str.toString() : ''; + this._string = str; + } + get Length(){ + return this._string.length; + } + Append(str){ + this._string += str; + } + AppendLine(str){ + if (typeof str !== 'undefined') this.Append(str); + this._string += "\n"; + } + AppendFormat(format){ + //taken from http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format + var args = Array.prototype.slice.call(arguments, 1); + return format.replace(/{(\d+)}/g, function(match, number){ + return typeof args[number] != 'undefined' ? args[number] : match; + }); + } + toString(){ + return this._string; + } +} \ No newline at end of file From 89d3206106444801cd50a62f656a060db564f218 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Sat, 1 Oct 2016 19:48:12 +0200 Subject: [PATCH 3/8] Using StringBuilder class, fix #30 --- engine/Container.js | 36 +++++++++++++++++++----------------- engine/Divert.js | 17 +++++++++-------- engine/Story.js | 19 ++++++++++--------- engine/StoryState.js | 7 ++++--- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/engine/Container.js b/engine/Container.js index 2fda8ea4..c6339711 100644 --- a/engine/Container.js +++ b/engine/Container.js @@ -1,5 +1,6 @@ import {StringValue} from './Value'; import {StoryException} from './StoryException'; +import {StringBuilder} from './StringBuilder'; import {Object as InkObject} from './Object'; export class Container extends InkObject{//also implements INamedContent. Not sure how to do it cleanly in JS. @@ -210,28 +211,30 @@ export class Container extends InkObject{//also implements INamedContent. Not su } BuildStringOfHierarchy(sb, indentation, pointedObj){ if (arguments.length == 0){ - return this.BuildStringOfHierarchy('', 0, null); + var sb = new StringBuilder(); + this.BuildStringOfHierarchy(sb, 0, null); + return sb.toString(); } function appendIndentation(){ var spacesPerIndent = 4; for(var i = 0; i < spacesPerIndent*indentation; ++i) { - sb += " "; + sb.Append(" "); } } appendIndentation(); - sb += "["; + sb.Append("["); if (this.hasValidName) { - sb += " (" + this.name + ")"; + sb.AppendFormat(" ({0})", this.name); } if (this == pointedObj) { - sb += " <---"; + sb.Append(" <---"); } - sb += "\n"; + sb.AppendLine(); indentation++; @@ -248,23 +251,23 @@ export class Container extends InkObject{//also implements INamedContent. Not su } else { appendIndentation(); if (obj instanceof StringValue) { - sb += "\""; - sb += obj.toString().replace("\n", "\\n"); - sb += "\""; + sb.Append("\""); + sb.Append(obj.toString().replace("\n", "\\n")); + sb.Append("\""); } else { - sb += obj.toString(); + sb.Append(obj.toString()); } } if (i != this.content.length - 1) { - sb += ","; + sb.Append(","); } if ( !(obj instanceof Container) && obj == pointedObj ) { - sb += " <---"; + sb.Append(" <---"); } - sb += "\n"; + sb.AppendLine(); } @@ -280,15 +283,14 @@ export class Container extends InkObject{//also implements INamedContent. Not su if (Object.keys(onlyNamed).length > 0) { appendIndentation(); - sb += "\n"; - sb += "-- named: --"; + sb.AppendLine("-- named: --"); for (var key in onlyNamed){ if (!(onlyNamed[key] instanceof Container)) console.warn("Can only print out named Containers"); var container = onlyNamed[key]; container.BuildStringOfHierarchy(sb, indentation, pointedObj); - sb += "\n"; + sb.Append("\n"); } } @@ -296,6 +298,6 @@ export class Container extends InkObject{//also implements INamedContent. Not su indentation--; appendIndentation(); - sb += "]"; + sb.Append("]"); } } \ No newline at end of file diff --git a/engine/Divert.js b/engine/Divert.js index fa07a091..963f3313 100644 --- a/engine/Divert.js +++ b/engine/Divert.js @@ -1,5 +1,6 @@ import {Path} from './Path'; import {PushPopType} from './PushPop'; +import {StringBuilder} from './StringBuilder'; import {Object as InkObject} from './Object'; export class Divert extends InkObject{ @@ -84,7 +85,7 @@ export class Divert extends InkObject{ return "Divert(null)"; } else { - var sb = ''; + var sb = new StringBuilder; var targetStr = this.targetPath.toString(); // int? targetLineNum = DebugLineNumberOfPath (targetPath); @@ -93,20 +94,20 @@ export class Divert extends InkObject{ targetStr = "line " + targetLineNum; } - sb += "Divert"; + sb.Append("Divert"); if (this.pushesToStack) { if (this.stackPushType == PushPopType.Function) { - sb += " function"; + sb.Append(" function"); } else { - sb += " tunnel"; + sb.Append(" tunnel"); } } - sb += " ("; - sb += targetStr; - sb += ")"; + sb.Append(" ("); + sb.Append(targetStr); + sb.Append(")"); - return sb; + return sb.toString(); } } } \ No newline at end of file diff --git a/engine/Story.js b/engine/Story.js index 8be1e56d..9b642c33 100644 --- a/engine/Story.js +++ b/engine/Story.js @@ -16,6 +16,7 @@ import {NativeFunctionCall} from './NativeFunctionCall'; import {StoryException} from './StoryException'; import {PRNG} from './PRNG'; import {Polyfill} from './Polyfill'; +import {StringBuilder} from './StringBuilder'; export class Story extends InkObject{ constructor(jsonString){ @@ -272,13 +273,13 @@ export class Story extends InkObject{ return this.currentText; } ContinueMaximally(){ - var sb = ''; + var sb = new StringBuilder(); while (this.canContinue) { - sb += this.Continue(); + sb.Append(this.Continue()); } - return sb; + return sb.toString(); } ContentAtPath(path){ return this.mainContentContainer.ContentAtPath(path); @@ -673,14 +674,14 @@ export class Story extends InkObject{ contentStackForString = contentStackForString.reverse(); // Build string out of the content we collected - var sb = ''; + var sb = new StringBuilder(); contentStackForString.forEach(c => { - sb += c.toString(); + sb.APpend(c.toString()); }); // Return to expression evaluation (from content mode) this.state.inExpressionEvaluation = true; - this.state.PushEvaluationStack(new StringValue(sb)); + this.state.PushEvaluationStack(new StringValue(sb.toString())); break; case ControlCommand.CommandType.ChoiceCount: @@ -930,9 +931,9 @@ export class Story extends InkObject{ this.state.currentContentObject = funcContainer; // Evaluate the function, and collect the string output - var stringOutput = ''; + var stringOutput = new StringBuilder(); while (this.canContinue) { - stringOutput += this.Continue(); + stringOutput.Append(this.Continue()); } textOutput = stringOutput.toString(); @@ -1178,7 +1179,7 @@ export class Story extends InkObject{ } } BuildStringOfHierarchy(){ - var sb = ""; + var sb = new StringBuilder; this.mainContentContainer.BuildStringOfHierarchy(sb, 0, this.state.currentContentObject); diff --git a/engine/StoryState.js b/engine/StoryState.js index 43c606f9..d72fcd86 100644 --- a/engine/StoryState.js +++ b/engine/StoryState.js @@ -5,6 +5,7 @@ import {Glue} from './Glue'; import {Path} from './Path'; import {ControlCommand} from './ControlCommand'; import {StoryException} from './StoryException'; +import {StringBuilder} from './StringBuilder'; import {JsonSerialisation} from './JsonSerialisation'; import {Story} from './Story'; import {PRNG} from './PRNG'; @@ -136,17 +137,17 @@ export class StoryState{ return false; } get currentText(){ - var sb = ''; + var sb = new StringBuilder(); this._outputStream.forEach(outputObj => { // var textContent = outputObj as StringValue; var textContent = outputObj; if (textContent instanceof StringValue) { - sb += textContent.value; + sb.APpend(textContent.value); } }); - return sb; + return sb.toString(); } get outputStream(){ return this._outputStream; From 625197eb4f8bd6124e8a16b703bac699f3384c9c Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Mon, 3 Oct 2016 15:00:13 +0200 Subject: [PATCH 4/8] Fix typos --- engine/Story.js | 2 +- engine/StoryState.js | 2 +- test/simple.js | 14 ++++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/engine/Story.js b/engine/Story.js index 9b642c33..e9dbf338 100644 --- a/engine/Story.js +++ b/engine/Story.js @@ -676,7 +676,7 @@ export class Story extends InkObject{ // Build string out of the content we collected var sb = new StringBuilder(); contentStackForString.forEach(c => { - sb.APpend(c.toString()); + sb.Append(c.toString()); }); // Return to expression evaluation (from content mode) diff --git a/engine/StoryState.js b/engine/StoryState.js index d72fcd86..bbf43e3d 100644 --- a/engine/StoryState.js +++ b/engine/StoryState.js @@ -143,7 +143,7 @@ export class StoryState{ // var textContent = outputObj as StringValue; var textContent = outputObj; if (textContent instanceof StringValue) { - sb.APpend(textContent.value); + sb.Append(textContent.value); } }); diff --git a/test/simple.js b/test/simple.js index 970cdaa2..4aaec619 100644 --- a/test/simple.js +++ b/test/simple.js @@ -1,4 +1,4 @@ -var Story = require('../dist/ink.js').Story; +var Story = require('../dist/ink-es2015.js').Story; var fs = require('fs'); var readline = require('readline'); @@ -9,9 +9,15 @@ var rl = readline.createInterface({ var inkFile = fs.readFileSync(__dirname + '/stories/test.ink.json', 'UTF-8').replace(/^\uFEFF/, ''); var s = new Story(inkFile); -console.log(s.ToJsonString()); -var out = s.ToJsonString(); -s = new Story(out); +//console.log(s.ToJsonString()); +//var out = s.ToJsonString(); +//s = new Story(out); + +s.BindExternalFunction('multiply', function(){ + return 5; +}); + +console.log(s.BuildStringOfHierarchy()); //end(); var gameSave; From 2adb3efe2fc2f35997b711c3a009baa015323faf Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Mon, 3 Oct 2016 18:53:16 +0200 Subject: [PATCH 5/8] Port of tags --- engine/JsonSerialisation.js | 12 +++++++++++ engine/Story.js | 42 +++++++++++++++++++++++++++++++++++-- engine/StoryState.js | 14 +++++++++++++ engine/Tag.js | 11 ++++++++++ engine/VariablesState.js | 4 ++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 engine/Tag.js diff --git a/engine/JsonSerialisation.js b/engine/JsonSerialisation.js index 6b74ae94..ba544181 100644 --- a/engine/JsonSerialisation.js +++ b/engine/JsonSerialisation.js @@ -9,6 +9,7 @@ import {VariableReference} from './VariableReference'; import {VariableAssignment} from './VariableAssignment'; import {NativeFunctionCall} from './NativeFunctionCall'; import {Void} from './Void'; +import {Tag} from './Tag'; import {Path} from './Path'; import {Choice} from './Choice'; import {Object as InkObject} from './Object'; @@ -223,6 +224,9 @@ export class JsonSerialisation{ varAss.isGlobal = isGlobalVar; return varAss; } + if (propValue = obj["#"]){ + return new Tag(propValue.toString()); + } if (obj["originalChoicePath"] != null) return this.JObjectToChoice(obj); @@ -378,6 +382,14 @@ export class JsonSerialisation{ var voidObj = obj; if (voidObj instanceof Void) return "void"; + +// var tag = obj as Tag; + var tag = obj; + if (tag instanceof Tag) { + var jObj = {}; + jObj["#"] = tag.text; + return jObj; + } // Used when serialising save state only // var choice = obj as Choice; diff --git a/engine/Story.js b/engine/Story.js index e9dbf338..26fd5025 100644 --- a/engine/Story.js +++ b/engine/Story.js @@ -10,6 +10,7 @@ import {Divert} from './Divert'; import {Value, StringValue, IntValue, DivertTargetValue, VariablePointerValue} from './Value'; import {Path} from './Path'; import {Void} from './Void'; +import {Tag} from './Tag'; import {VariableAssignment} from './VariableAssignment'; import {VariableReference} from './VariableReference'; import {NativeFunctionCall} from './NativeFunctionCall'; @@ -22,7 +23,7 @@ export class Story extends InkObject{ constructor(jsonString){ super(); - this.inkVersionCurrent = 13; + this.inkVersionCurrent = 14; this.inkVersionMinimumCompatible = 12; this._variableObservers = null; @@ -79,6 +80,9 @@ export class Story extends InkObject{ get currentText(){ return this.state.currentText; } + get currentTags(){ + return this.state.currentTags; + } get currentErrors(){ return this.state.currentErrors; } @@ -103,6 +107,10 @@ export class Story extends InkObject{ return this.state.currentContentObject != null && !this.state.hasError; } + get globalTags(){ + return this.TagsAtStartOfFlowContainerWithPathString(""); + } + ToJsonString(){ var rootContainerJsonList = JsonSerialisation.RuntimeObjectToJToken(this._mainContentContainer); @@ -191,9 +199,10 @@ export class Story extends InkObject{ // Cover cases that non-text generated content was evaluated last step var currText = this.currentText; var prevTextLength = stateAtLastNewline.currentText.length; + var prevTagCount = stateAtLastNewline.currentTags.length; // Output has been extended? - if( currText !== stateAtLastNewline.currentText ) { + if( currText !== stateAtLastNewline.currentText || prevTagCount != this.currentTags.length ) { // Original newline still exists? if( currText.length >= prevTextLength && currText[prevTextLength-1] == '\n' ) { @@ -1178,6 +1187,35 @@ export class Story extends InkObject{ }); } } + TagsForContentAtPath(path){ + return this.TagsAtStartOfFlowContainerWithPathString(path); + } + TagsAtStartOfFlowContainerWithPathString(pathString){ + var path = new Path(pathString); + + // Expected to be global story, knot or stitch +// var flowContainer = ContentAtPath (path) as Container; + var flowContainer = this.ContentAtPath(path); + + // First element of the above constructs is a compiled weave +// var innerWeaveContainer = flowContainer.content [0] as Container; + var innerWeaveContainer = flowContainer.content[0]; + + // Any initial tag objects count as the "main tags" associated with that story/knot/stitch + var tags = null; + + innerWeaveContainer.content.every(c => { +// var tag = c as Runtime.Tag; + var tag = c; + if (tag instanceof Tag) { + if (tags == null) tags = []; + tags.push(tag.text); + return true; + } else return false; + }); + + return tags; + } BuildStringOfHierarchy(){ var sb = new StringBuilder; diff --git a/engine/StoryState.js b/engine/StoryState.js index bbf43e3d..a083eec2 100644 --- a/engine/StoryState.js +++ b/engine/StoryState.js @@ -1,6 +1,7 @@ import {CallStack} from './CallStack'; import {VariablesState} from './VariablesState'; import {StringValue} from './Value'; +import {Tag} from './Tag'; import {Glue} from './Glue'; import {Path} from './Path'; import {ControlCommand} from './ControlCommand'; @@ -149,6 +150,19 @@ export class StoryState{ return sb.toString(); } + get currentTags(){ + var tags = []; + + this._outputStream.forEach(outputObj => { +// var tag = outputObj as Tag; + var tag = outputObj; + if (tag instanceof Tag) { + tags.push(tag.text); + } + }); + + return tags; + } get outputStream(){ return this._outputStream; } diff --git a/engine/Tag.js b/engine/Tag.js new file mode 100644 index 00000000..78fd43ba --- /dev/null +++ b/engine/Tag.js @@ -0,0 +1,11 @@ +export class Tag{ + constructor(tagText){ + this._text = tagText.toString() || ''; + } + get text(){ + return this._text; + } + toString(){ + return "# " + this._text; + } +} \ No newline at end of file diff --git a/engine/VariablesState.js b/engine/VariablesState.js index f57b2504..3ab8c9c9 100644 --- a/engine/VariablesState.js +++ b/engine/VariablesState.js @@ -236,6 +236,10 @@ export class VariablesState{ return null; } else{ + if (typeof this._globalVariables[variableName] === 'undefined'){ + throw new StoryException("Variable '" + variableName + "' doesn't exist, so can't be set."); + } + var val = Value.Create(value); if (val == null) { if (value == null) { From aec10883384bfb05b088e1610967a7bfb368b587 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Wed, 5 Oct 2016 21:43:06 +0200 Subject: [PATCH 6/8] Fix #28 --- engine/Story.js | 10 +++++----- test/simple.js | 10 ++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/engine/Story.js b/engine/Story.js index e9dbf338..ec54ae60 100644 --- a/engine/Story.js +++ b/engine/Story.js @@ -2,12 +2,13 @@ import {Container} from './Container'; import {Object as InkObject} from './Object'; import {JsonSerialisation} from './JsonSerialisation'; import {StoryState} from './StoryState'; +import {CallStack} from './CallStack'; import {ControlCommand} from './ControlCommand'; import {PushPopType} from './PushPop'; import {ChoicePoint} from './ChoicePoint'; import {Choice} from './Choice'; import {Divert} from './Divert'; -import {Value, StringValue, IntValue, DivertTargetValue, VariablePointerValue} from './Value'; +import {ValueType, Value, StringValue, IntValue, DivertTargetValue, VariablePointerValue} from './Value'; import {Path} from './Path'; import {Void} from './Void'; import {VariableAssignment} from './VariableAssignment'; @@ -894,7 +895,6 @@ export class Story extends InkObject{ try { funcContainer = this.ContentAtPath(new Path(functionName)); } catch (e) { - console.log(e); if (e.message.indexOf("not found") >= 0) throw "Function doesn't exist: '" + functionName + "'"; else @@ -917,17 +917,17 @@ export class Story extends InkObject{ this.state.callStack.currentElement.currentContentIndex = 0; if (args != null) { - for (var i = 0; i < args.Length; i++) { + for (var i = 0; i < args.length; i++) { if (!(typeof args[i] === 'number' || typeof args[i] === 'string')) { throw "ink arguments when calling EvaluateFunction must be int, float or string"; } - this.state.evaluationStack.Add(Runtime.Value.Create(args[i])); + this.state.evaluationStack.push(Value.Create(args[i])); } } // Jump into the function! - this.state.callStack.push(PushPopType.Function); + this.state.callStack.Push(PushPopType.Function); this.state.currentContentObject = funcContainer; // Evaluate the function, and collect the string output diff --git a/test/simple.js b/test/simple.js index 4aaec619..e639d5aa 100644 --- a/test/simple.js +++ b/test/simple.js @@ -9,15 +9,9 @@ var rl = readline.createInterface({ var inkFile = fs.readFileSync(__dirname + '/stories/test.ink.json', 'UTF-8').replace(/^\uFEFF/, ''); var s = new Story(inkFile); -//console.log(s.ToJsonString()); -//var out = s.ToJsonString(); -//s = new Story(out); -s.BindExternalFunction('multiply', function(){ - return 5; -}); - -console.log(s.BuildStringOfHierarchy()); +//console.log(s.BuildStringOfHierarchy()); +console.log(s.EvaluateFunction('describe_health', [50])); //end(); var gameSave; From ecf8483d8b076e6c552cd0d1a312ea3650a42364 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Wed, 5 Oct 2016 22:01:38 +0200 Subject: [PATCH 7/8] Fix #29 --- engine/Story.js | 19 +++++++++++-------- test/simple.js | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/engine/Story.js b/engine/Story.js index ec54ae60..1af8029d 100644 --- a/engine/Story.js +++ b/engine/Story.js @@ -880,9 +880,9 @@ export class Story extends InkObject{ return false; } } - EvaluateFunction(functionName, textOutput, args){ - //match the first signature of the function - if (typeof textOutput !== 'string') return this.EvaluateFunction(functionName, '', textOutput); + EvaluateFunction(functionName, args, returnTextOutput){ + //EvaluateFunction behaves slightly differently than the C# version. In C#, you can pass a (second) parameter `out textOutput` to get the text outputted by the function. This is not possible in js. Instead, we maintain the regular signature (functionName, args), plus an optional third parameter returnTextOutput. If set to true, we will return both the textOutput and the returned value, as an object. + returnTextOutput = !!returnTextOutput; if (functionName == null) { throw "Function is null"; @@ -935,7 +935,7 @@ export class Story extends InkObject{ while (this.canContinue) { stringOutput.Append(this.Continue()); } - textOutput = stringOutput.toString(); + var textOutput = stringOutput.toString(); // Restore original stack this.state.callStack = originalCallstack; @@ -950,10 +950,13 @@ export class Story extends InkObject{ if (returnedObj == null) returnedObj = poppedObj; } + + //inkjs specific: since we change the type of return conditionally, we want to have only one return statement + var returnedValue = null; if (returnedObj) { if (returnedObj instanceof Void) - return null; + returnedValue = null; // Some kind of value, if not void // var returnVal = returnedObj as Runtime.Value; @@ -962,15 +965,15 @@ export class Story extends InkObject{ // DivertTargets get returned as the string of components // (rather than a Path, which isn't public) if (returnVal.valueType == ValueType.DivertTarget) { - return returnVal.valueObject.toString(); + returnedValue = returnVal.valueObject.toString(); } // Other types can just have their exact object type: // int, float, string. VariablePointers get returned as strings. - return returnVal.valueObject; + returnedValue = returnVal.valueObject; } - return null; + return (returnTextOutput) ? {'returned': returnedValue, 'output': textOutput} : returnedValue; } EvaluateExpression(exprContainer){ var startCallStackHeight = this.state.callStack.elements.length; diff --git a/test/simple.js b/test/simple.js index e639d5aa..1d35f017 100644 --- a/test/simple.js +++ b/test/simple.js @@ -11,7 +11,7 @@ var inkFile = fs.readFileSync(__dirname + '/stories/test.ink.json', 'UTF-8').rep var s = new Story(inkFile); //console.log(s.BuildStringOfHierarchy()); -console.log(s.EvaluateFunction('describe_health', [50])); +console.log(s.EvaluateFunction('describe_health', [50], true)); //end(); var gameSave; From d88f912f7aefd3869f009f4e1a45cb68e1e3a698 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Fri, 7 Oct 2016 19:32:07 +0200 Subject: [PATCH 8/8] bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 408039ba..4aeb712d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inkjs", - "version": "1.2.0", + "version": "1.3.0", "description": "A javascript port of inkle's ink scripting language (http://www.inklestudios.com/ink/)", "main": "dist/ink-es2015.js", "scripts": {