From e6d91c38d29b9113165c8421ecde41778b31a663 Mon Sep 17 00:00:00 2001 From: Yannick Lohse Date: Sun, 5 Mar 2017 13:12:04 +0100 Subject: [PATCH] Updated templates --- templates/browser_serverless/ink.js | 1799 +++++++++++++++++++++----- templates/browser_with_server/ink.js | 1799 +++++++++++++++++++++----- 2 files changed, 2914 insertions(+), 684 deletions(-) diff --git a/templates/browser_serverless/ink.js b/templates/browser_serverless/ink.js index 65f976dd..cce524b7 100644 --- a/templates/browser_serverless/ink.js +++ b/templates/browser_serverless/ink.js @@ -2,78 +2,7 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('inkjs', ['exports'], factory) : (factory((global.inkjs = global.inkjs || {}))); -}(this, function (exports) { 'use strict'; - - var babelHelpers = {}; - babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; - }; - - babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - }; - - babelHelpers.createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - - babelHelpers.extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; - }; - - babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - }; - - babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return call && (typeof call === "object" || typeof call === "function") ? call : self; - }; - - babelHelpers; +}(this, (function (exports) { 'use strict'; var Path$1 = function () { function Path() /*polymorphic constructor*/{ @@ -218,7 +147,10 @@ var componentStrings = componentsStr.split('.'); componentStrings.forEach(function (str) { - if (!isNaN(parseInt(str))) { + //we need to distinguish between named components that start with a number, eg "42somewhere", and indexed components + //the normal parseInt won't do for the detection because it's too relaxed. + //see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt + if (/^(\-|\+)?([0-9]+|Infinity)$/.test(str)) { _this.components.push(new Component(parseInt(str))); } else { _this.components.push(new Component(str)); @@ -303,7 +235,7 @@ Path$1.parentId = "^"; Path$1.Component = Component; - var InkObject = function () { + var Object$1 = function () { function Object() { babelHelpers.classCallCheck(this, Object); @@ -448,15 +380,405 @@ return Object; }(); + var StringBuilder = function () { + function StringBuilder(str) { + babelHelpers.classCallCheck(this, StringBuilder); + + str = typeof str !== 'undefined' ? str.toString() : ''; + this._string = str; + } + + babelHelpers.createClass(StringBuilder, [{ + key: 'Append', + value: function Append(str) { + this._string += str; + } + }, { + key: 'AppendLine', + value: function AppendLine(str) { + if (typeof str !== 'undefined') this.Append(str); + this._string += "\n"; + } + }, { + key: 'AppendFormat', + value: function 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; + }); + } + }, { + key: 'toString', + value: function toString() { + return this._string; + } + }, { + key: 'Length', + get: function get() { + return this._string.length; + } + }]); + return StringBuilder; + }(); + + var RawListItem = function () { + function RawListItem(fullNameOrOriginName, itemName) { + babelHelpers.classCallCheck(this, RawListItem); + + if (itemName !== undefined) { + this.originName = fullNameOrOriginName; + this.itemName = itemName; + } else { + var nameParts = fullNameOrOriginName.toString().split('.'); + this.originName = nameParts[0]; + this.itemName = nameParts[1]; + } + } + + babelHelpers.createClass(RawListItem, [{ + key: 'isNull', + value: function isNull() { + return this.originName == null && this.itemName == null; + } + }, { + key: 'toString', + value: function toString() { + return this.fullname; + } + }, { + key: 'Equals', + value: function Equals(obj) { + if (obj instanceof RawListItem) { + // var otherItem = (RawListItem)obj; + var otherItem = obj; + return otherItem.itemName == this.itemName && otherItem.originName == this.originName; + } + + return false; + } + //GetHashCode not implemented + + }, { + key: 'toString', + value: function toString() { + //WARNING: experimental. RawListItem are structs and are used as keys inside hashes. In js, we can't use an object as a key, as the key needs to be a string. C# gets around that with the internal GetHashCode, and the js equivalent to that is toString. So here, toString acts as C#'s GetHashCode + var originCode = '0'; + var itemCode = this.itemName ? this.itemName.toString() : 'null'; + if (this.originName != null) originCode = this.originName.toString(); + + return originCode + itemCode; + } + }, { + key: 'fullName', + get: function get() { + return (this.originName !== null ? this.originName : "?") + "." + this.itemName; + } + }], [{ + key: 'Null', + value: function Null() { + return new RawListItem(null, null); + } + }]); + return RawListItem; + }(); + + //in C#, rawlists are based on dictionnary; the equivalent of a dictionnary in js is Object, but we can't use that or it will conflate dictionnary items and RawList class properties. + //instead RawList-js has a special _values property wich contains the actual "Dictionnary", and a few Dictionnary methods are re-implemented on RawList. This also means directly iterating over the RawList won't work as expected. Maybe we can return a proxy if that's required. + var RawList = function () { + function RawList(otherListOrSingleElement) { + var _this = this; + + babelHelpers.classCallCheck(this, RawList); + + this._keys = {}; + this._values = {}; + this.origins = null; + this._originNames = null; + + //polymorphioc constructor + if (otherListOrSingleElement) { + if (otherListOrSingleElement instanceof RawList) { + var otherList = otherListOrSingleElement; + otherList.forEach(function (kv) { + _this.Add(kv.Key, kv.Value); + }); + + this._originNames = otherList._originNames; + } else if (otherListOrSingleElement.hasOwnProperty('Key') && otherListOrSingleElement.hasOwnProperty('Value')) { + var singleElement = otherListOrSingleElement; + this.Add(singleElement.Key, singleElement.Value); + } + } + } + + babelHelpers.createClass(RawList, [{ + key: 'forEach', + value: function forEach(fn) { + for (var key in this._values) { + fn({ + Key: this._keys[key], + Value: this._values[key] + }); + } + } + }, { + key: 'ContainsKey', + value: function ContainsKey(key) { + return key in this._values; + } + }, { + key: 'Add', + value: function Add(key, value) { + this._keys[key] = key; + this._values[key] = value; + } + }, { + key: 'Remove', + value: function Remove(key) { + delete this._values[key]; + delete this._keys[key]; + } + }, { + key: 'SetInitialOriginName', + value: function SetInitialOriginName(initialOriginName) { + this._originNames = [initialOriginName]; + } + }, { + key: 'SetInitialOriginNames', + value: function SetInitialOriginNames(initialOriginNames) { + this._originNames = initialOriginNames.slice(); //store a copy + } + }, { + key: 'Union', + value: function Union(otherList) { + var union = new RawList(this); + otherList.forEach(function (kv) { + union.Add(kv.Key, kv.Value); + }); + return union; + } + }, { + key: 'Intersect', + value: function Intersect(otherList) { + var intersection = new RawList(); + this.forEach(function (kv) { + if (otherList.ContainsKey(kv.Key)) intersection.Add(kv.Key, kv.Value); + }); + return intersection; + } + }, { + key: 'Without', + value: function Without(listToRemove) { + var result = new RawList(this); + listToRemove.forEach(function (kv) { + result.Remove(kv.Key); + }); + return result; + } + }, { + key: 'Contains', + value: function Contains(otherList) { + var _this2 = this; + + var contains = true; + otherList.forEach(function (kv) { + if (!_this2.ContainsKey(kv.Key)) contains = false; + }); + return contains; + } + }, { + key: 'GreaterThan', + value: function GreaterThan(otherList) { + if (this.Count == 0) return false; + if (otherList.Count == 0) return true; + + // All greater + return this.minItem.Value > otherList.maxItem.Value; + } + }, { + key: 'GreaterThanOrEquals', + value: function GreaterThanOrEquals(otherList) { + if (this.Count == 0) return false; + if (otherList.Count == 0) return true; + + return this.minItem.Value >= otherList.minItem.Value && this.maxItem.Value >= otherList.maxItem.Value; + } + }, { + key: 'LessThan', + value: function LessThan(otherList) { + if (otherList.Count == 0) return false; + if (this.Count == 0) return true; + + return this.maxItem.Value < otherList.minItem.Value; + } + }, { + key: 'LessThanOrEquals', + value: function LessThanOrEquals(otherList) { + if (otherList.Count == 0) return false; + if (this.Count == 0) return true; + + return this.maxItem.Value <= otherList.maxItem.Value && this.minItem.Value <= otherList.minItem.Value; + } + }, { + key: 'MaxAsList', + value: function MaxAsList() { + if (this.Count > 0) return new RawList(this.maxItem);else return new RawList(); + } + }, { + key: 'MinAsList', + value: function MinAsList() { + if (this.Count > 0) return new RawList(this.minItem);else return new RawList(); + } + }, { + key: 'Equals', + value: function Equals(other) { + // var otherRawList = other as RawList; + var otherRawList = other; + if (otherRawList instanceof RawList === false) return false; + if (otherRawList.Count != this.Count) return false; + + var equals = true; + this.forEach(function (kv) { + if (!otherRawList.ContainsKey(kv.Key)) equals = false; + }); + + return equals; + } + //GetHashCode not implemented + + }, { + key: 'toString', + value: function toString() { + var ordered = []; + this.forEach(function (kv) { + ordered.push(kv); + }); + ordered = ordered.sort(function (a, b) { + return a.Value === b.Value ? 0 : a.Value > b.Value ? 1 : -1; + }); + + var sb = new StringBuilder(); + for (var i = 0; i < ordered.length; i++) { + if (i > 0) sb.Append(", "); + + var item = ordered[i].Key; + sb.Append(item.itemName); + } + + return sb.toString(); + } + //casting a RawList to a Number, for somereason, actually gives a number. This messes up the type detection when creating a Value from a RawList. Returning NaN here prevents that. + + }, { + key: 'valueOf', + value: function valueOf() { + return NaN; + } + }, { + key: 'Count', + get: function get() { + return Object.keys(this._values).length; + } + }, { + key: 'originOfMaxItem', + get: function get() { + if (this.origins == null) return null; + + var maxOriginName = this.maxItem.Key.originName; + var result = null; + this.origins.every(function (origin) { + if (origin.name == maxOriginName) { + result = origin; + return false; + } else return true; + }); + + return result; + } + }, { + key: 'originNames', + get: function get() { + var _this3 = this; + + if (this.Count > 0) { + if (this._originNames == null && this.Count > 0) this._originNames = [];else this._originNames.length = 0; + + this.forEach(function (itemAndValue) { + _this3._originNames.push(itemAndValue.Key.originName); + }); + } + + return this._originNames; + } + }, { + key: 'maxItem', + get: function get() { + var max = { + Key: null, + Value: null + }; + this.forEach(function (kv) { + if (max.Key === null || kv.Value > max.Value) max = kv; + }); + + return max; + } + }, { + key: 'minItem', + get: function get() { + var min = { + Key: null, + Value: null + }; + this.forEach(function (kv) { + if (min.Key === null || kv.Value < min.Value) min = kv; + }); + + return min; + } + }, { + key: 'inverse', + get: function get() { + var _this4 = this; + + var list = new RawList(); + if (this.origins != null) { + this.origins.forEach(function (origin) { + origin.items.forEach(function (itemAndValue) { + if (!_this4.ContainsKey(itemAndValue.Key)) list.Add(itemAndValue.Key, itemAndValue.Value); + }); + }); + } + return list; + } + }, { + key: 'all', + get: function get() { + var list = new RawList(); + if (this.origins != null) { + this.origins.forEach(function (origin) { + origin.items.forEach(function (itemAndValue) { + list.Add(itemAndValue.Key, itemAndValue.Value); + }); + }); + } + return list; + } + }]); + return RawList; + }(); + var ValueType = { // Used in coersion Int: 0, Float: 1, - String: 2, + List: 2, + String: 3, // Not used for coersion described above - DivertTarget: 3, - VariablePointer: 4 + DivertTarget: 4, + VariablePointer: 5 }; var AbstractValue = function (_InkObject) { @@ -465,7 +787,7 @@ function AbstractValue(val) { babelHelpers.classCallCheck(this, AbstractValue); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(AbstractValue).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (AbstractValue.__proto__ || Object.getPrototypeOf(AbstractValue)).call(this)); _this._valueType; _this._isTruthy; @@ -481,7 +803,7 @@ }, { key: 'Copy', value: function Copy(val) { - return this.Create(val); + return AbstractValue.Create(val); } }, { key: 'valueType', @@ -515,13 +837,15 @@ return new StringValue(val); } else if (val instanceof Path$1) { return new DivertTargetValue(val); + } else if (val instanceof RawList) { + return new ListValue(val); } return null; } }]); return AbstractValue; - }(InkObject); + }(Object$1); var Value = function (_AbstractValue) { babelHelpers.inherits(Value, _AbstractValue); @@ -529,7 +853,7 @@ function Value(val) { babelHelpers.classCallCheck(this, Value); - var _this2 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Value).call(this)); + var _this2 = babelHelpers.possibleConstructorReturn(this, (Value.__proto__ || Object.getPrototypeOf(Value)).call(this)); _this2.value = val; return _this2; @@ -563,7 +887,7 @@ function IntValue(val) { babelHelpers.classCallCheck(this, IntValue); - var _this3 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(IntValue).call(this, val || 0)); + var _this3 = babelHelpers.possibleConstructorReturn(this, (IntValue.__proto__ || Object.getPrototypeOf(IntValue)).call(this, val || 0)); _this3._valueType = ValueType.Int; return _this3; @@ -606,7 +930,7 @@ function FloatValue(val) { babelHelpers.classCallCheck(this, FloatValue); - var _this4 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(FloatValue).call(this, val || 0.0)); + var _this4 = babelHelpers.possibleConstructorReturn(this, (FloatValue.__proto__ || Object.getPrototypeOf(FloatValue)).call(this, val || 0.0)); _this4._valueType = ValueType.Float; return _this4; @@ -649,7 +973,7 @@ function StringValue(val) { babelHelpers.classCallCheck(this, StringValue); - var _this5 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(StringValue).call(this, val || '')); + var _this5 = babelHelpers.possibleConstructorReturn(this, (StringValue.__proto__ || Object.getPrototypeOf(StringValue)).call(this, val || '')); _this5._valueType = ValueType.String; @@ -730,7 +1054,7 @@ function DivertTargetValue(targetPath) { babelHelpers.classCallCheck(this, DivertTargetValue); - var _this6 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(DivertTargetValue).call(this, targetPath)); + var _this6 = babelHelpers.possibleConstructorReturn(this, (DivertTargetValue.__proto__ || Object.getPrototypeOf(DivertTargetValue)).call(this, targetPath)); _this6._valueType = ValueType.DivertTarget; return _this6; @@ -771,7 +1095,7 @@ function VariablePointerValue(variableName, contextIndex) { babelHelpers.classCallCheck(this, VariablePointerValue); - var _this7 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(VariablePointerValue).call(this, variableName)); + var _this7 = babelHelpers.possibleConstructorReturn(this, (VariablePointerValue.__proto__ || Object.getPrototypeOf(VariablePointerValue)).call(this, variableName)); _this7._valueType = ValueType.VariablePointer; _this7.contextIndex = typeof contextIndex !== 'undefined' ? contextIndex : -1; @@ -812,13 +1136,87 @@ return VariablePointerValue; }(Value); + var ListValue = function (_Value6) { + babelHelpers.inherits(ListValue, _Value6); + babelHelpers.createClass(ListValue, [{ + key: 'Cast', + value: function Cast(newType) { + if (newType == ValueType.Int) { + var max = this.value.maxItem; + if (max.Key.isNull) return new IntValue(0);else return new IntValue(max.Value); + } else if (newType == ValueType.Float) { + var max = this.value.maxItem; + if (max.Key.isNull) return new FloatValue(0.0);else return new FloatValue(parseFloat(max.Value)); + } else if (newType == ValueType.String) { + var max = value.maxItem; + if (max.Key.isNull) return new StringValue("");else { + return new StringValue(max.Key.toString()); + } + } + + if (newType == this.valueType) return this; + + throw "Unexpected type cast of Value to new ValueType"; + } + }, { + key: 'valueType', + get: function get() { + return ValueType.List; + } + }, { + key: 'isTruthy', + get: function get() { + var isTruthy = false; + this.value.forEach(function (kv) { + var listItemIntValue = kv.Value; + if (listItemIntValue != 0) isTruthy = true; + }); + return isTruthy; + } + }]); + + function ListValue(listOrSingleItem, singleValue) { + babelHelpers.classCallCheck(this, ListValue); + + var _this8 = babelHelpers.possibleConstructorReturn(this, (ListValue.__proto__ || Object.getPrototypeOf(ListValue)).call(this, null)); + + _this8._valueType = ValueType.List; + + if (listOrSingleItem instanceof RawList) { + _this8.value = new RawList(listOrSingleItem); + } else if (listOrSingleItem !== undefined && singleValue !== undefined) { + _this8.value = new RawList({ + Key: listOrSingleItem, + Value: singleValue + }); + } else { + _this8.value = new RawList(); + } + return _this8; + } + + babelHelpers.createClass(ListValue, null, [{ + key: 'RetainListOriginsForAssignment', + value: function RetainListOriginsForAssignment(oldValue, newValue) { + // var oldList = oldValue as ListValue; + var oldList = oldValue; + // var newList = newValue as ListValue; + var newList = newValue; + + // When assigning the emtpy list, try to retain any initial origin names + if (oldList instanceof ListValue && newList instanceof ListValue && newList.value.Count == 0) newList.value.SetInitialOriginNames(oldList.value.originNames); + } + }]); + return ListValue; + }(Value); + var StoryException = function (_Error) { babelHelpers.inherits(StoryException, _Error); function StoryException(message) { babelHelpers.classCallCheck(this, StoryException); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(StoryException).call(this, message)); + var _this = babelHelpers.possibleConstructorReturn(this, (StoryException.__proto__ || Object.getPrototypeOf(StoryException)).call(this, message)); _this.message = message; _this.name = 'StoryException'; @@ -828,56 +1226,14 @@ return StoryException; }(Error); - var StringBuilder = function () { - function StringBuilder(str) { - babelHelpers.classCallCheck(this, StringBuilder); - - str = typeof str !== 'undefined' ? str.toString() : ''; - this._string = str; - } - - babelHelpers.createClass(StringBuilder, [{ - key: 'Append', - value: function Append(str) { - this._string += str; - } - }, { - key: 'AppendLine', - value: function AppendLine(str) { - if (typeof str !== 'undefined') this.Append(str); - this._string += "\n"; - } - }, { - key: 'AppendFormat', - value: function 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; - }); - } - }, { - key: 'toString', - value: function toString() { - return this._string; - } - }, { - key: 'Length', - get: function get() { - return this._string.length; - } - }]); - return StringBuilder; - }(); - var Container = function (_InkObject) { babelHelpers.inherits(Container, _InkObject); - //also implements INamedContent. Not sure how to do it cleanly in JS. + //also implements INamedContent. Not sure how to do it cleanly in JS. function Container() { babelHelpers.classCallCheck(this, Container); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Container).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this)); _this.name = ''; @@ -931,7 +1287,7 @@ }, { key: 'AddToNamedContentOnly', value: function AddToNamedContentOnly(namedContentObj) { - if (namedContentObj instanceof InkObject === false) console.warn("Can only add Runtime.Objects to a Runtime.Container"); + if (namedContentObj instanceof Object$1 === false) console.warn("Can only add Runtime.Objects to a Runtime.Container"); namedContentObj.parent = this; this.namedContent[namedContentObj.name] = namedContentObj; @@ -1193,13 +1549,18 @@ } }]); return Container; - }(InkObject); + }(Object$1); + + var Glue = function (_InkObject) { + babelHelpers.inherits(Glue, _InkObject); - var Glue = function () { function Glue(type) { babelHelpers.classCallCheck(this, Glue); - this.glueType = type; + var _this = babelHelpers.possibleConstructorReturn(this, (Glue.__proto__ || Object.getPrototypeOf(Glue)).call(this)); + + _this.glueType = type; + return _this; } babelHelpers.createClass(Glue, [{ @@ -1233,7 +1594,7 @@ } }]); return Glue; - }(); + }(Object$1); var GlueType = { Bidirectional: 0, @@ -1247,7 +1608,7 @@ function ControlCommand(commandType) { babelHelpers.classCallCheck(this, ControlCommand); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(ControlCommand).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (ControlCommand.__proto__ || Object.getPrototypeOf(ControlCommand)).call(this)); _this._commandType = typeof commandType != 'undefined' ? commandType : CommandType.NotSet; return _this; @@ -1363,9 +1724,19 @@ value: function End() { return new ControlCommand(CommandType.End); } + }, { + key: 'ListFromInt', + value: function ListFromInt() { + return new ControlCommand(CommandType.ListFromInt); + } + }, { + key: 'ListRange', + value: function ListRange() { + return new ControlCommand(CommandType.ListRange); + } }]); return ControlCommand; - }(InkObject); + }(Object$1); var CommandType = { NotSet: -1, @@ -1387,7 +1758,9 @@ SequenceShuffleIndex: 15, StartThread: 16, Done: 17, - End: 18 + End: 18, + ListFromInt: 19, + ListRange: 20 }; CommandType.TOTAL_VALUES = Object.keys(CommandType).length - 1; //-1 because NotSet shoudn't count ControlCommand.CommandType = CommandType; @@ -1403,7 +1776,7 @@ function Divert(stackPushType) { babelHelpers.classCallCheck(this, Divert); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Divert).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (Divert.__proto__ || Object.getPrototypeOf(Divert)).call(this)); _this._targetPath; _this._targetContent; @@ -1522,7 +1895,7 @@ } }]); return Divert; - }(InkObject); + }(Object$1); var ChoicePoint = function (_InkObject) { babelHelpers.inherits(ChoicePoint, _InkObject); @@ -1530,9 +1903,9 @@ function ChoicePoint(onceOnly) { babelHelpers.classCallCheck(this, ChoicePoint); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(ChoicePoint).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (ChoicePoint.__proto__ || Object.getPrototypeOf(ChoicePoint)).call(this)); - _this.pathOnChoice; + _this._pathOnChoice; _this.hasCondition; _this.hasStartContent; _this.hasChoiceOnlyContent; @@ -1556,11 +1929,25 @@ return "Choice: -> " + targetString; } + }, { + key: 'pathOnChoice', + get: function get() { + if (this._pathOnChoice != null && this._pathOnChoice.isRelative) { + var choiceTargetObj = this.choiceTarget; + if (choiceTargetObj) { + this._pathOnChoice = choiceTargetObj.path; + } + } + return this._pathOnChoice; + }, + set: function set(value) { + this._pathOnChoice = value; + } }, { key: 'choiceTarget', get: function get() { - //return this.ResolvePath (pathOnChoice) as Container; - return this.ResolvePath(this.pathOnChoice); + //return this.ResolvePath (_pathOnChoice) as Container; + return this.ResolvePath(this._pathOnChoice); } }, { key: 'pathStringOnChoice', @@ -1590,7 +1977,7 @@ } }]); return ChoicePoint; - }(InkObject); + }(Object$1); var VariableReference = function (_InkObject) { babelHelpers.inherits(VariableReference, _InkObject); @@ -1598,7 +1985,7 @@ function VariableReference(name) { babelHelpers.classCallCheck(this, VariableReference); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(VariableReference).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (VariableReference.__proto__ || Object.getPrototypeOf(VariableReference)).call(this)); _this.name = name; _this.pathForCount; @@ -1632,7 +2019,7 @@ } }]); return VariableReference; - }(InkObject); + }(Object$1); var VariableAssignment = function (_InkObject) { babelHelpers.inherits(VariableAssignment, _InkObject); @@ -1640,7 +2027,7 @@ function VariableAssignment(variableName, isNewDeclaration) { babelHelpers.classCallCheck(this, VariableAssignment); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(VariableAssignment).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (VariableAssignment.__proto__ || Object.getPrototypeOf(VariableAssignment)).call(this)); _this._variableName = variableName || null; _this._isNewDeclaration = !!isNewDeclaration; @@ -1651,7 +2038,7 @@ babelHelpers.createClass(VariableAssignment, [{ key: "toString", value: function toString() { - return "VarAssign to " + this.variableName;; + return "VarAssign to " + this.variableName; } }, { key: "variableName", @@ -1665,26 +2052,27 @@ } }]); return VariableAssignment; - }(InkObject); + }(Object$1); var Void = function (_InkObject) { babelHelpers.inherits(Void, _InkObject); function Void() { babelHelpers.classCallCheck(this, Void); - return babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Void).apply(this, arguments)); + return babelHelpers.possibleConstructorReturn(this, (Void.__proto__ || Object.getPrototypeOf(Void)).apply(this, arguments)); } return Void; - }(InkObject); + }(Object$1); + //misses delegates, probably the returns from function calls var NativeFunctionCall = function (_InkObject) { babelHelpers.inherits(NativeFunctionCall, _InkObject); function NativeFunctionCall(name) { babelHelpers.classCallCheck(this, NativeFunctionCall); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(NativeFunctionCall).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (NativeFunctionCall.__proto__ || Object.getPrototypeOf(NativeFunctionCall)).call(this)); _this.name = name; _this._numberOfParameters; @@ -1708,14 +2096,20 @@ throw "Unexpected number of parameters"; } + var hasList = false; parameters.forEach(function (p) { if (p instanceof Void) throw new StoryException("Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?"); + if (p instanceof ListValue) hasList = true; }); + if (parameters.length == 2 && hasList) { + return this.CallBinaryListOperation(parameters); + } + var coercedParams = this.CoerceValuesToSingleType(parameters); var coercedType = coercedParams[0].valueType; - //Originally CallType gets a type parameter taht is used to do some casting, but we can do without. + //Originally CallType gets a type parameter that is used to do some casting, but we can do without. if (coercedType == ValueType.Int) { return this.CallType(coercedParams); } else if (coercedType == ValueType.Float) { @@ -1724,6 +2118,8 @@ return this.CallType(coercedParams); } else if (coercedType == ValueType.DivertTarget) { return this.CallType(coercedParams); + } else if (coercedType == ValueType.List) { + return this.CallType(coercedParams); } return null; @@ -1742,7 +2138,7 @@ var opForTypeObj = this._operationFuncs[valType]; if (!opForTypeObj) { - throw new StoryException("Can not perform operation '" + this.name + "' on " + valType); + throw new StoryException("Cannot perform operation '" + this.name + "' on " + valType); } // Binary @@ -1772,11 +2168,73 @@ throw "Unexpected number of parameters to NativeFunctionCall: " + parametersOfSingleType.length; } } + }, { + key: 'CallBinaryListOperation', + value: function CallBinaryListOperation(parameters) { + // List-Int addition/subtraction returns a List (e.g. "alpha" + 1 = "beta") + if ((this.name == "+" || this.name == "-") && parameters[0] instanceof ListValue && parameters[1] instanceof IntValue) return this.CallListIncrementOperation(parameters); + + // var v1 = parameters [0] as Value; + var v1 = parameters[0]; + // var v2 = parameters [1] as Value; + var v2 = parameters[1]; + + // And/or with any other type requires coerscion to bool (int) + if ((this.name == "&&" || this.name == "||") && (v1.valueType != ValueType.List || v2.valueType != ValueType.List)) { + // var op = _operationFuncs [ValueType.Int] as BinaryOp; + var op = this._operationFuncs[ValueType.Int]; + var result = op(v1.isTruthy ? 1 : 0, v2.isTruthy ? 1 : 0); + return parseInt(result); + } + + // Normal (list • list) operation + if (v1.valueType == ValueType.List && v2.valueType == ValueType.List) return this.CallType([v1, v2]); + + throw new StoryException("Can not call use '" + this.name + "' operation on " + v1.valueType + " and " + v2.valueType); + } + }, { + key: 'CallListIncrementOperation', + value: function CallListIncrementOperation(listIntParams) { + var _this2 = this; + + var listVal = listIntParams[0]; + var intVal = listIntParams[1]; + + var resultRawList = new RawList(); + + listVal.value.forEach(function (listItemWithValue) { + var listItem = listItemWithValue.Key; + var listItemValue = listItemWithValue.Value; + + // Find + or - operation + var intOp = _this2._operationFuncs[ValueType.Int]; + + // Return value unknown until it's evaluated + var targetInt = intOp(listItemValue, intVal.value); + + // Find this item's origin (linear search should be ok, should be short haha) + var itemOrigin = null; + listVal.value.origins.forEach(function (origin) { + if (origin.name == listItem.originName) { + itemOrigin = origin; + return false; + } + }); + if (itemOrigin != null) { + var incrementedItem = itemOrigin.TryGetItemWithValue(targetInt); + if (incrementedItem.exists) resultRawList.Add(incrementedItem.item, targetInt); + } + }); + + return new ListValue(resultRawList); + } }, { key: 'CoerceValuesToSingleType', value: function CoerceValuesToSingleType(parametersIn) { var valType = ValueType.Int; + var specialCaseList = null; + // Find out what the output type is // "higher level" types infect both so that binary operations // use the same type on both sides. e.g. binary operation of @@ -1786,14 +2244,40 @@ if (val.valueType > valType) { valType = val.valueType; } + + if (val.valueType == ValueType.List) { + // specialCaseList = val as ListValue; + specialCaseList = val; + } }); // Coerce to this chosen type var parametersOut = []; - parametersIn.forEach(function (val) { - var castedValue = val.Cast(valType); - parametersOut.push(castedValue); - }); + + if (valType == ValueType.List) { + parametersIn.forEach(function (val) { + if (val.valueType == ValueType.List) { + parametersOut.push(val); + } else if (val.valueType == ValueType.Int) { + var intVal = parseInt(val.valueObject); + var list = specialCaseList.value.originOfMaxItem; + + var item = list.TryGetItemWithValue(intVal); + if (item.exists) { + var castedValue = new ListValue(item.item, intVal); + parametersOut.push(castedValue); + } else throw new StoryException("Could not find List item with the value " + intVal + " in " + list.name); + } else throw new StoryException("Cannot mix Lists and " + val.valueType + " values in this operation"); + }); + } + + // Normal Coercing (with standard casting) + else { + parametersIn.forEach(function (val) { + var castedValue = val.Cast(valType); + parametersOut.push(castedValue); + }); + } return parametersOut; } @@ -1868,7 +2352,7 @@ return x * y; }); this.AddIntBinaryOp(this.Divide, function (x, y) { - return x / y; + return parseInt(x / y); }); this.AddIntBinaryOp(this.Mod, function (x, y) { return x % y; @@ -1977,6 +2461,67 @@ return x === y ? 1 : 0; }); + this.AddListBinaryOp(this.Add, function (x, y) { + return x.Union(y); + }); + this.AddListBinaryOp(this.And, function (x, y) { + return x.Union(y); + }); + this.AddListBinaryOp(this.Subtract, function (x, y) { + return x.Without(y); + }); + this.AddListBinaryOp(this.Has, function (x, y) { + return x.Contains(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.Hasnt, function (x, y) { + return x.Contains(y) ? 0 : 1; + }); + this.AddListBinaryOp(this.Intersect, function (x, y) { + return x.Intersect(y); + }); + + this.AddListBinaryOp(this.Equal, function (x, y) { + return x.Equals(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.Greater, function (x, y) { + return x.GreaterThan(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.Less, function (x, y) { + return x.LessThan(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.GreaterThanOrEquals, function (x, y) { + return x.GreaterThanOrEquals(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.LessThanOrEquals, function (x, y) { + return x.LessThanOrEquals(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.NotEquals, function (x, y) { + return !x.Equals(y) ? 1 : 0; + }); + + this.AddListUnaryOp(this.Not, function (x) { + return x.Count == 0 ? 1 : 0; + }); + + this.AddListUnaryOp(this.Invert, function (x) { + return x.inverse; + }); + this.AddListUnaryOp(this.All, function (x) { + return x.all; + }); + this.AddListUnaryOp(this.ListMin, function (x) { + return x.MinAsList(); + }); + this.AddListUnaryOp(this.ListMax, function (x) { + return x.MaxAsList(); + }); + this.AddListUnaryOp(this.Count, function (x) { + return x.Count; + }); + this.AddListUnaryOp(this.ValueOfList, function (x) { + return x.maxItem.Value; + }); + // Special case: The only operation you can do on divert target values var divertTargetsEqual = function divertTargetsEqual(d1, d2) { return d1.Equals(d2) ? 1 : 0; @@ -2020,16 +2565,26 @@ value: function AddStringBinaryOp(name, op) { this.AddOpToNativeFunc(name, 2, ValueType.String, op); } + }, { + key: 'AddListBinaryOp', + value: function AddListBinaryOp(name, op) { + this.AddOpToNativeFunc(name, 2, ValueType.List, op); + } + }, { + key: 'AddListUnaryOp', + value: function AddListUnaryOp(name, op) { + this.AddOpToNativeFunc(name, 1, ValueType.List, op); + } }]); return NativeFunctionCall; - }(InkObject); + }(Object$1); NativeFunctionCall.Add = "+"; NativeFunctionCall.Subtract = "-"; NativeFunctionCall.Divide = "/"; NativeFunctionCall.Multiply = "*"; NativeFunctionCall.Mod = "%"; - NativeFunctionCall.Negate = "~"; + NativeFunctionCall.Negate = "_"; NativeFunctionCall.Equal = "=="; NativeFunctionCall.Greater = ">"; @@ -2045,28 +2600,44 @@ NativeFunctionCall.Min = "MIN"; NativeFunctionCall.Max = "MAX"; + NativeFunctionCall.Has = "?"; + NativeFunctionCall.Hasnt = "!?"; + NativeFunctionCall.Intersect = "^"; + + NativeFunctionCall.ListMin = "LIST_MIN"; + NativeFunctionCall.ListMax = "LIST_MAX"; + NativeFunctionCall.All = "LIST_ALL"; + NativeFunctionCall.Count = "LIST_COUNT"; + NativeFunctionCall.ValueOfList = "LIST_VALUE"; + NativeFunctionCall.Invert = "LIST_INVERT"; + NativeFunctionCall._nativeFunctions = null; - var Tag = function () { + var Tag = function (_InkObject) { + babelHelpers.inherits(Tag, _InkObject); + function Tag(tagText) { babelHelpers.classCallCheck(this, Tag); - this._text = tagText.toString() || ''; + var _this = babelHelpers.possibleConstructorReturn(this, (Tag.__proto__ || Object.getPrototypeOf(Tag)).call(this)); + + _this._text = tagText.toString() || ''; + return _this; } babelHelpers.createClass(Tag, [{ - key: "toString", + key: 'toString', value: function toString() { return "# " + this._text; } }, { - key: "text", + key: 'text', get: function get() { return this._text; } }]); return Tag; - }(); + }(Object$1); var Choice = function () { function Choice(choice) { @@ -2088,10 +2659,167 @@ get: function get() { return this.choicePoint.pathStringOnChoice; } + }, { + key: "sourcePath", + get: function get() { + return this.choicePoint.path.componentsString; + } }]); return Choice; }(); + var ListDefinition = function () { + function ListDefinition(name, items) { + babelHelpers.classCallCheck(this, ListDefinition); + + this._name = name || ''; + this._items = null; + this._rawListItemsKeys = null; + this._itemNameToValues = items || {}; + } + + babelHelpers.createClass(ListDefinition, [{ + key: 'forEachItems', + value: function forEachItems(fn) { + for (var key in this._rawListItemsKeys) { + fn({ + Key: this._rawListItemsKeys[key], + Value: this._items[key] + }); + } + } + }, { + key: 'ValueForItem', + value: function ValueForItem(item) { + var intVal = this._itemNameToValues[item.itemName]; + if (intVal !== undefined) return intVal;else return 0; + } + }, { + key: 'ContainsItem', + value: function ContainsItem(item) { + if (item.originName != this.name) return false; + + return item.itemName in this._itemNameToValues; + } + }, { + key: 'TryGetItemWithValue', + value: function TryGetItemWithValue(val, item) { + //item was an out + //the original function returns a boolean and has a second parameter called item that is an `out`. Both are needed and we can't just return the item because it'll always be truthy. Instead, we return an object containing the bool an dthe item + for (var key in this._itemNameToValues) { + if (this._itemNameToValues[key] == val) { + item = new RawListItem(this.name, key); + return { + item: item, + exists: true + }; + } + } + + item = RawListItem.Null; + return { + item: item, + exists: false + }; + } + }, { + key: 'ListRange', + value: function ListRange(min, max) { + var rawList = new RawList(); + for (var key in this._itemNameToValues) { + if (this._itemNameToValues[key] >= min && this._itemNameToValues[key] <= max) { + var item = new RawListItem(this.name, key); + rawList.Add(item, this._itemNameToValues[key]); + } + } + return new ListValue(rawList); + } + }, { + key: 'name', + get: function get() { + return this._name; + } + }, { + key: 'items', + get: function get() { + if (this._items == null) { + this._items = {}; + this._rawListItemsKeys = {}; + for (var key in this._itemNameToValues) { + var item = new RawListItem(this.name, key); + this._rawListItemsKeys[item] = item; + this._items[item] = this._itemNameToValues[key]; + } + } + this._items.forEach = this.forEachItems.bind(this); + + return this._items; + } + }]); + return ListDefinition; + }(); + + var ListDefinitionsOrigin = function () { + function ListDefinitionsOrigin(lists) { + var _this = this; + + babelHelpers.classCallCheck(this, ListDefinitionsOrigin); + + this._lists = {}; + + lists.forEach(function (list) { + _this._lists[list.name] = list; + }); + } + + babelHelpers.createClass(ListDefinitionsOrigin, [{ + key: 'TryGetDefinition', + value: function TryGetDefinition(name, def) { + //initially, this function returns a boolean and the second parameter is an out. + return name in this._lists ? this._lists[name] : def; + } + }, { + key: 'FindSingleItemListWithName', + value: function FindSingleItemListWithName(name) { + var item = RawListItem.Null; + var list = null; + + var nameParts = name.split('.'); + if (nameParts.length == 2) { + item = new RawListItem(nameParts[0], nameParts[1]); + list = this.TryGetDefinition(item.originName, list); + } else { + for (var key in this._lists) { + var listWithItem = this._lists[key]; + item = new RawListItem(key, name); + if (listWithItem.ContainsItem(item)) { + list = listWithItem; + break; + } + } + } + + if (list != null) { + var itemValue = list.ValueForItem(item); + return new ListValue(item, itemValue); + } + + return null; + } + }, { + key: 'lists', + get: function get() { + var listOfLists = []; + + for (var key in this._lists) { + listOfLists.push(this._lists[key]); + } + return listOfLists; + } + }]); + return ListDefinitionsOrigin; + }(); + var JsonSerialisation = function () { function JsonSerialisation() { babelHelpers.classCallCheck(this, JsonSerialisation); @@ -2143,7 +2871,7 @@ for (var key in dictionary) { // var runtimeObj = keyVal.Value as Runtime.Object; var runtimeObj = dictionary[key]; - if (runtimeObj instanceof InkObject) jsonObj[key] = this.RuntimeObjectToJToken(runtimeObj); + if (runtimeObj instanceof Object$1) jsonObj[key] = this.RuntimeObjectToJToken(runtimeObj); } return jsonObj; @@ -2194,6 +2922,7 @@ } // Native functions + if (str == "L^") str = "^"; if (NativeFunctionCall.CallExistsWithName(str)) return NativeFunctionCall.CallWithName(str); // Pop @@ -2305,6 +3034,28 @@ return new Tag(propValue.toString()); } + //list value + if (propValue = obj["list"]) { + // var listContent = (Dictionary)propValue; + var listContent = propValue; + var rawList = new RawList(); + if (propValue = obj["origins"]) { + // var namesAsObjs = (List)propValue; + var namesAsObjs = propValue; + // rawList.SetInitialOriginNames(namesAsObjs.Cast().ToList()); + rawList.SetInitialOriginNames(namesAsObjs); + } + + for (var key in listContent) { + var nameToVal = listContent[key]; + var item = new RawListItem(key); + var val = parseInt(nameToVal); + rawList.Add(item, val); + } + + return new ListValue(rawList); + } + if (obj["originalChoicePath"] != null) return this.JObjectToChoice(obj); } @@ -2372,6 +3123,12 @@ if (strVal.isNewline) return "\n";else return "^" + strVal.value; } + // var listVal = obj as ListValue; + var listVal = obj; + if (listVal instanceof ListValue) { + return this.InkListToJObject(listVal); + } + // var divTargetVal = obj as DivertTargetValue; var divTargetVal = obj; if (divTargetVal instanceof DivertTargetValue) return { @@ -2399,7 +3156,13 @@ // var nativeFunc = obj as Runtime.NativeFunctionCall; var nativeFunc = obj; - if (nativeFunc instanceof NativeFunctionCall) return nativeFunc.name; + if (nativeFunc instanceof NativeFunctionCall) { + var name = nativeFunc.name; + + // Avoid collision with ^ used to indicate a string + if (name == "^") name = "L^"; + return name; + } // Variable reference // var varRef = obj as VariableReference; @@ -2551,6 +3314,75 @@ jObj["originalThreadIndex"] = choice.originalThreadIndex; return jObj; } + }, { + key: 'InkListToJObject', + value: function InkListToJObject(listVal) { + var rawList = listVal.value; + + var dict = {}; + + var content = {}; + + rawList.forEach(function (itemAndValue) { + var item = itemAndValue.Key; + var val = itemAndValue.Value; + content[item.toString()] = val; + }); + + dict["list"] = content; + + if (rawList.Count == 0 && rawList.originNames != null && rawList.originNames.length > 0) { + // dict["origins"] = rawList.originNames.Cast ().ToList (); + dict["origins"] = rawList.originNames; + } + + return dict; + } + }, { + key: 'ListDefinitionsToJToken', + value: function ListDefinitionsToJToken(origin) { + var result = {}; + + origin.lists.forEach(function (def) { + var listDefJson = {}; + def.items.forEach(function (itemToVal) { + var item = itemToVal.Key; + var val = itemToVal.Value; + listDefJson[item.itemName] = val; + }); + + result[def.name] = listDefJson; + }); + + return result; + } + }, { + key: 'JTokenToListDefinitions', + value: function JTokenToListDefinitions(obj) { + // var defsObj = (Dictionary)obj; + var defsObj = obj; + + var allDefs = []; + + for (var key in defsObj) { + var name = key.toString(); + // var listDefJson = (Dictionary)kv.Value; + var listDefJson = defsObj[key]; + + // Cast (string, object) to (string, int) for items + var items = {}; + + for (var nameValueKey in listDefJson) { + var nameValue = listDefJson[nameValueKey]; + items[nameValueKey] = parseInt(nameValue); + } + + var def = new ListDefinition(name, items); + allDefs.push(def); + } + + return new ListDefinitionsOrigin(allDefs); + } }]); return JsonSerialisation; }(); @@ -2576,6 +3408,8 @@ _controlCommandNames[ControlCommand.CommandType.StartThread] = "thread"; _controlCommandNames[ControlCommand.CommandType.Done] = "done"; _controlCommandNames[ControlCommand.CommandType.End] = "end"; + _controlCommandNames[ControlCommand.CommandType.ListFromInt] = "listInt"; + _controlCommandNames[ControlCommand.CommandType.ListRange] = "range"; for (var i$1 = 0; i$1 < ControlCommand.CommandType.TOTAL_VALUES; ++i$1) { if (_controlCommandNames[i$1] == null) throw "Control command not accounted for in serialisation"; @@ -2784,8 +3618,8 @@ if (this.canPopThread) { this._threads.splice(this._threads.indexOf(this.currentThread), 1); //should be equivalent to a pop() } else { - throw "Can't pop thread"; - } + throw "Can't pop thread"; + } } }, { key: 'SetJsonToken', @@ -2850,6 +3684,9 @@ throw new StoryException("Could not find temporary variable to set: " + name); } + var oldValue; + if (oldValue = contextElement.temporaryVariables[name]) ListValue.RetainListOriginsForAssignment(oldValue, value); + contextElement.temporaryVariables[name] = value; } }, { @@ -2920,12 +3757,16 @@ return CallStack; }(); + //still needs: + // - varchanged events + // - see if the internal getenumarators are needed var VariablesState = function () { - function VariablesState(callStack) { + function VariablesState(callStack, listDefsOrigin) { babelHelpers.classCallCheck(this, VariablesState); this._globalVariables = {}; this._callStack = callStack; + this._listDefsOrigin = listDefsOrigin; this._batchObservingVariableChanges = null; this._changedVariables = null; @@ -2978,15 +3819,16 @@ } }, { key: 'CopyFrom', - value: function CopyFrom(varState) { - this._globalVariables = varState._globalVariables; - this.variableChangedEvent = varState.variableChangedEvent; + value: function CopyFrom(toCopy) { + this._globalVariables = babelHelpers.extends({}, toCopy._globalVariables); + + this.variableChangedEvent = toCopy.variableChangedEvent; - if (varState.batchObservingVariableChanges != this.batchObservingVariableChanges) { + if (toCopy.batchObservingVariableChanges != this.batchObservingVariableChanges) { - if (varState.batchObservingVariableChanges) { + if (toCopy.batchObservingVariableChanges) { this._batchObservingVariableChanges = true; - this._changedVariables = varState._changedVariables; + this._changedVariables = toCopy._changedVariables; } else { this._batchObservingVariableChanges = false; this._changedVariables = null; @@ -3017,6 +3859,9 @@ // 0 context = global if (contextIndex == 0 || contextIndex == -1) { if (varValue = this._globalVariables[name]) return varValue; + + var listItemValue = this._listDefsOrigin.FindSingleItemListWithName(name); + if (listItemValue) return listItemValue; } // Temporary @@ -3078,12 +3923,24 @@ this._callStack.SetTemporaryVariable(name, value, varAss.isNewDeclaration, contextIndex); } } + }, { + key: 'RetainListOriginsForAssignment', + value: function RetainListOriginsForAssignment(oldValue, newValue) { + // var oldList = oldValue as ListValue; + var oldList = oldValue; + // var newList = newValue as ListValue; + var newList = newValue; + + if (oldList instanceof ListValue && newList instanceof ListValue && newList.value.Count == 0) newList.value.SetInitialOriginNames(oldList.value.originNames); + } }, { key: 'SetGlobal', value: function SetGlobal(variableName, value) { var oldValue = null; oldValue = this._globalVariables[variableName]; + ListValue.RetainListOriginsForAssignment(oldValue, value); + this._globalVariables[variableName] = value; if (this.variableChangedEvent != null && value !== oldValue) { @@ -3105,7 +3962,7 @@ var valueOfVariablePointedTo = this.GetRawVariableWithName(varPointer.variableName, contextIndex); // Extra layer of indirection: - // When accessing a pointer to a pointer (e.g. when calling nested or + // When accessing a pointer to a pointer (e.g. when calling nested or // recursive functions that take a variable references, ensure we don't create // a chain of indirection by just returning the final target. // var doubleRedirectionPointer = valueOfVariablePointedTo as VariablePointerValue; @@ -3168,7 +4025,7 @@ this._changedVariables = []; } - // Finished observing variables in a batch - now send + // Finished observing variables in a batch - now send // notifications for changed variables all in one go. else { if (this._changedVariables != null) { @@ -3225,11 +4082,14 @@ this.story = story; this._outputStream = []; + this._outputStreamTextDirty = true; + this._outputStreamTagsDirty = true; + this.OutputStreamDirty(); this._evaluationStack = []; this.callStack = new CallStack(story.rootContentContainer); - this._variablesState = new VariablesState(this.callStack); + this._variablesState = new VariablesState(this.callStack, story.listDefinitions); this._visitCounts = {}; this._turnIndices = {}; @@ -3242,14 +4102,37 @@ this.previousRandom = 0; this._currentChoices = []; + this._currentText = null; + this._currentTags = null; this._currentErrors = null; this.didSafeExit = false; + this._isExternalFunctionEvaluation = false; + this._originalCallstack = null; + this._originalEvaluationStackHeight = 0; + this.GoToStart(); } babelHelpers.createClass(StoryState, [{ + key: 'MatchRightGlueForLeftGlue', + value: function MatchRightGlueForLeftGlue(leftGlue) { + if (!leftGlue.isLeft) return null; + + for (var i = this._outputStream.length - 1; i >= 0; i--) { + var c = this._outputStream[i]; + // var g = c as Glue; + var g = c; + if (g instanceof Glue && g.isRight && g.parent == leftGlue.parent) { + return g; + } else if (c instanceof ControlCommand) // e.g. BeginString + break; + } + + return null; + } + }, { key: 'GoToStart', value: function GoToStart() { this.callStack.currentElement.currentContainer = this.story.mainContentContainer; @@ -3264,10 +4147,33 @@ key: 'ResetOutput', value: function ResetOutput() { this._outputStream.length = 0; + this.OutputStreamDirty(); } }, { key: 'PushEvaluationStack', value: function PushEvaluationStack(obj) { + var _this = this; + + // var listValue = obj as ListValue; + var listValue = obj; + if (listValue instanceof ListValue) { + + // Update origin when list is has something to indicate the list origin + var rawList = listValue.value; + var names = rawList.originNames; + if (names != null) { + var origins = []; + + names.forEach(function (n) { + var def = null; + def = _this.story.listDefinitions.TryGetDefinition(n, def); + if (origins.indexOf(def) < 0) origins.push(def); + }); + + rawList.origins = origins; + } + } + this.evaluationStack.push(obj); } }, { @@ -3293,7 +4199,7 @@ }, { key: 'PushToOutputStream', value: function PushToOutputStream(obj) { - var _this = this; + var _this2 = this; // var text = obj as StringValue; var text = obj; @@ -3301,13 +4207,14 @@ var listText = this.TrySplittingHeadTailWhitespace(text); if (listText != null) { listText.forEach(function (textObj) { - _this.PushToOutputStreamIndividual(textObj); + _this2.PushToOutputStreamIndividual(textObj); }); return; } } this.PushToOutputStreamIndividual(obj); + this.OutputStreamDirty(); } }, { key: 'TrySplittingHeadTailWhitespace', @@ -3382,12 +4289,15 @@ // Found matching left-glue for right-glue? Close it. var existingRightGlue = this.currentRightGlue; var foundMatchingLeftGlue = !!(glue.isLeft && existingRightGlue && glue.parent == existingRightGlue.parent); + var matchingRightGlue = null; - // Left/Right glue is auto-generated for inline expressions + if (glue.isLeft) matchingRightGlue = this.MatchRightGlueForLeftGlue(glue); + + // Left/Right glue is auto-generated for inline expressions // where we want to absorb newlines but only in a certain direction. // "Bi" glue is written by the user in their ink with <> if (glue.isLeft || glue.isBi) { - this.TrimNewlinesFromOutputStream(foundMatchingLeftGlue); + this.TrimNewlinesFromOutputStream(matchingRightGlue); } includeInOutput = glue.isBi || glue.isRight; @@ -3403,7 +4313,7 @@ includeInOutput = false; } - // Able to completely reset when + // Able to completely reset when else if (text.isNonWhitespace) { this.RemoveExistingGlue(); } @@ -3414,11 +4324,12 @@ if (includeInOutput) { this._outputStream.push(obj); + this.OutputStreamDirty(); } } }, { key: 'TrimNewlinesFromOutputStream', - value: function TrimNewlinesFromOutputStream(stopAndRemoveRightGlue) { + value: function TrimNewlinesFromOutputStream(rightGlueToStopAt) { var removeWhitespaceFrom = -1; var rightGluePos = -1; var foundNonWhitespace = false; @@ -3439,8 +4350,8 @@ if (cmd instanceof ControlCommand || txt instanceof StringValue && txt.isNonWhitespace) { foundNonWhitespace = true; - if (!stopAndRemoveRightGlue) break; - } else if (stopAndRemoveRightGlue && glue instanceof Glue && glue.isRight) { + if (rightGlueToStopAt == null) break; + } else if (rightGlueToStopAt && glue instanceof Glue && glue == rightGlueToStopAt) { rightGluePos = i; break; } else if (txt instanceof StringValue && txt.isNewline && !foundNonWhitespace) { @@ -3463,9 +4374,18 @@ } } - // Remove the glue (it will come before the whitespace, - // so index is still valid) - if (stopAndRemoveRightGlue && rightGluePos > -1) this._outputStream.splice(rightGluePos, 1); + if (rightGlueToStopAt && rightGluePos > -1) { + i = rightGluePos; + while (i < this._outputStream.length) { + if (this._outputStream[i] instanceof Glue && this._outputStream[i].isRight) { + this.outputStream.splice(i, 1); + } else { + i++; + } + } + } + + this.OutputStreamDirty(); } }, { key: 'TrimFromExistingGlue', @@ -3476,6 +4396,8 @@ var txt = this._outputStream[i]; if (txt instanceof StringValue && !txt.isNonWhitespace) this._outputStream.splice(i, 1);else i++; } + + this.OutputStreamDirty(); } }, { key: 'RemoveExistingGlue', @@ -3489,17 +4411,20 @@ break; } } + + this.OutputStreamDirty(); } }, { key: 'ForceEnd', value: function ForceEnd() { - this.currentContentObject = null; - while (this.callStack.canPopThread) { this.callStack.PopThread(); }while (this.callStack.canPop) { this.callStack.Pop(); - }this.currentChoices.length = 0; + }this._currentChoices.length = 0; + + this.currentContentObject = null; + this.previousContentObject = null; this.didSafeExit = true; } @@ -3507,12 +4432,90 @@ key: 'SetChosenPath', value: function SetChosenPath(path) { // Changing direction, assume we need to clear current set of choices - this.currentChoices.length = 0; + this._currentChoices.length = 0; this.currentPath = path; this._currentTurnIndex++; } + }, { + key: 'StartExternalFunctionEvaluation', + value: function StartExternalFunctionEvaluation(funcContainer, args) { + // We'll start a new callstack, so keep hold of the original, + // as well as the evaluation stack so we know if the function + // returned something + this._originalCallstack = this.callStack; + this._originalEvaluationStackHeight = this.evaluationStack.length; + + // Create a new base call stack element. + this.callStack = new CallStack(funcContainer); + this.callStack.currentElement.type = PushPopType.Function; + + // By setting ourselves in external function evaluation mode, + // we're saying it's okay to end the flow without a Done or End, + // but with a ~ return instead. + this._isExternalFunctionEvaluation = true; + + // Pass arguments onto the evaluation stack + if (args != null) { + 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.PushEvaluationStack(Value.Create(args[i])); + } + } + } + }, { + key: 'TryExitExternalFunctionEvaluation', + value: function TryExitExternalFunctionEvaluation() { + if (this._isExternalFunctionEvaluation && this.callStack.elements.length == 1 && this.callStack.currentElement.type == PushPopType.Function) { + this.currentContentObject = null; + this.didSafeExit = true; + return true; + } + + return false; + } + }, { + key: 'CompleteExternalFunctionEvaluation', + value: function CompleteExternalFunctionEvaluation() { + // Do we have a returned value? + // Potentially pop multiple values off the stack, in case we need + // to clean up after ourselves (e.g. caller of EvaluateFunction may + // have passed too many arguments, and we currently have no way to check for that) + var returnedObj = null; + while (this.evaluationStack.length > this._originalEvaluationStackHeight) { + var poppedObj = this.PopEvaluationStack(); + if (returnedObj == null) returnedObj = poppedObj; + } + + // Restore our own state + this.callStack = this._originalCallstack; + this._originalCallstack = null; + this._originalEvaluationStackHeight = 0; + + if (returnedObj) { + if (returnedObj instanceof Void) return null; + + // Some kind of value, if not void + // var returnVal = returnedObj as Runtime.Value; + var returnVal = returnedObj; + + // 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(); + } + + // Other types can just have their exact object type: + // int, float, string. VariablePointers get returned as strings. + return returnVal.valueObject; + } + + return null; + } }, { key: 'AddError', value: function AddError(message) { @@ -3522,6 +4525,12 @@ this._currentErrors.push(message); } + }, { + key: 'OutputStreamDirty', + value: function OutputStreamDirty() { + this._outputStreamTextDirty = true; + this._outputStreamTagsDirty = true; + } }, { key: 'VisitCountAtPathString', value: function VisitCountAtPathString(pathString) { @@ -3536,7 +4545,9 @@ var copy = new StoryState(this.story); copy.outputStream.push.apply(copy.outputStream, this._outputStream); - copy.currentChoices.push.apply(copy.currentChoices, this.currentChoices); + this.OutputStreamDirty(); + + copy._currentChoices.push.apply(copy._currentChoices, this._currentChoices); if (this.hasError) { copy.currentErrors = []; @@ -3545,7 +4556,7 @@ copy.callStack = new CallStack(this.callStack); - copy._variablesState = new VariablesState(copy.callStack); + copy._variablesState = new VariablesState(copy.callStack, this.story.listDefinitions); copy.variablesState.CopyFrom(this.variablesState); copy.evaluationStack.push.apply(copy.evaluationStack, this.evaluationStack); @@ -3554,8 +4565,17 @@ copy.previousContentObject = this.previousContentObject; - copy._visitCounts = this._visitCounts; - copy._turnIndices = this._turnIndices; + copy._isExternalFunctionEvaluation = this._isExternalFunctionEvaluation; + + copy._visitCounts = {}; + for (var keyValue in this._visitCounts) { + copy._visitCounts[keyValue] = this._visitCounts[keyValue]; + } + copy._turnIndices = {}; + for (var keyValue in this._turnIndices) { + copy._turnIndices[keyValue] = this._turnIndices[keyValue]; + } + copy._currentTurnIndex = this.currentTurnIndex; copy.storySeed = this.storySeed; copy.previousRandom = this.previousRandom; @@ -3576,6 +4596,15 @@ } }, { key: 'currentChoices', + get: function get() { + // If we can continue generating text content rather than choices, + // then we reflect the choice list as being empty, since choices + // should always come at the end. + if (this.canContinue) return []; + return this._currentChoices; + } + }, { + key: 'generatedChoices', get: function get() { return this._currentChoices; } @@ -3612,6 +4641,11 @@ set: function set(value) { this.callStack.currentElement.currentObject = value; } + }, { + key: 'canContinue', + get: function get() { + return this.currentContentObject != null && !this.hasError; + } }, { key: 'hasError', get: function get() { @@ -3696,32 +4730,43 @@ }, { key: 'currentText', get: function get() { - var sb = new StringBuilder(); + if (this._outputStreamTextDirty) { + var sb = new StringBuilder(); - this._outputStream.forEach(function (outputObj) { - // var textContent = outputObj as StringValue; - var textContent = outputObj; - if (textContent instanceof StringValue) { - sb.Append(textContent.value); - } - }); + this._outputStream.forEach(function (outputObj) { + // var textContent = outputObj as StringValue; + var textContent = outputObj; + if (textContent instanceof StringValue) { + sb.Append(textContent.value); + } + }); - return sb.toString(); + this._currentText = sb.toString(); + this._outputStreamTextDirty = false; + } + + return this._currentText; } }, { key: 'currentTags', get: function get() { - var tags = []; + var _this3 = this; - this._outputStream.forEach(function (outputObj) { - // var tag = outputObj as Tag; - var tag = outputObj; - if (tag instanceof Tag) { - tags.push(tag.text); - } - }); + if (this._outputStreamTagsDirty) { + this._currentTags = []; - return tags; + this._outputStream.forEach(function (outputObj) { + // var tag = outputObj as Tag; + var tag = outputObj; + if (tag instanceof Tag) { + _this3._currentTags.push(tag.text); + } + }); + + this._outputStreamTagsDirty = false; + } + + return this._currentTags; } }, { key: 'outputStream', @@ -3754,16 +4799,16 @@ }, { key: 'jsonToken', get: function get() { - var _this2 = this; + var _this4 = this; var obj = {}; var choiceThreads = null; - this.currentChoices.forEach(function (c) { + this._currentChoices.forEach(function (c) { c.originalChoicePath = c.choicePoint.path.componentsString; c.originalThreadIndex = c.threadAtGeneration.threadIndex; - if (_this2.callStack.ThreadWithIndex(c.originalThreadIndex) == null) { + if (_this4.callStack.ThreadWithIndex(c.originalThreadIndex) == null) { if (choiceThreads == null) choiceThreads = {}; choiceThreads[c.originalThreadIndex.toString()] = c.threadAtGeneration.jsonToken; @@ -3779,7 +4824,7 @@ obj["outputStream"] = JsonSerialisation.ListToJArray(this._outputStream); - obj["currentChoices"] = JsonSerialisation.ListToJArray(this.currentChoices); + obj["currentChoices"] = JsonSerialisation.ListToJArray(this._currentChoices); if (this.divertedTargetObject != null) obj["currentDivertTarget"] = this.divertedTargetObject.path.componentsString; @@ -3796,7 +4841,7 @@ return obj; }, set: function set(value) { - var _this3 = this; + var _this5 = this; var jObject = value; @@ -3813,6 +4858,7 @@ this._evaluationStack = JsonSerialisation.JArrayToRuntimeObjList(jObject["evalStack"]); this._outputStream = JsonSerialisation.JArrayToRuntimeObjList(jObject["outputStream"]); + this.OutputStreamDirty(); // currentChoices = Json.JArrayToRuntimeObjList((JArray)jObject ["currentChoices"]); this._currentChoices = JsonSerialisation.JArrayToRuntimeObjList(jObject["currentChoices"]); @@ -3831,15 +4877,15 @@ // var jChoiceThreads = jObject["choiceThreads"] as JObject; var jChoiceThreads = jObject["choiceThreads"]; - this.currentChoices.forEach(function (c) { - c.choicePoint = _this3.story.ContentAtPath(new Path$1(c.originalChoicePath)); + this._currentChoices.forEach(function (c) { + c.choicePoint = _this5.story.ContentAtPath(new Path$1(c.originalChoicePath)); - var foundActiveThread = _this3.callStack.ThreadWithIndex(c.originalThreadIndex); + var foundActiveThread = _this5.callStack.ThreadWithIndex(c.originalThreadIndex); if (foundActiveThread != null) { c.threadAtGeneration = foundActiveThread; } else { var jSavedChoiceThread = jChoiceThreads[c.originalThreadIndex.toString()]; - c.threadAtGeneration = new CallStack.Thread(jSavedChoiceThread, _this3.story); + c.threadAtGeneration = new CallStack.Thread(jSavedChoiceThread, _this5.story); } }); } @@ -3847,8 +4893,8 @@ return StoryState; }(); - StoryState.kInkSaveStateVersion = 5; - StoryState.kMinCompatibleLoadVersion = 4; + StoryState.kInkSaveStateVersion = 6; + StoryState.kMinCompatibleLoadVersion = 6; if (!Number.isInteger) { Number.isInteger = function isInteger(nVal) { @@ -3859,21 +4905,27 @@ var Story = function (_InkObject) { babelHelpers.inherits(Story, _InkObject); - function Story(jsonString) { + function Story(jsonString, lists) { babelHelpers.classCallCheck(this, Story); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Story).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (Story.__proto__ || Object.getPrototypeOf(Story)).call(this)); + + lists = lists || null; - _this.inkVersionCurrent = 14; - _this.inkVersionMinimumCompatible = 12; + _this.inkVersionCurrent = 16; + _this.inkVersionMinimumCompatible = 16; _this._variableObservers = null; _this._externals = {}; + _this._prevContainerSet = null; + _this._listDefinitions = null; if (jsonString instanceof Container) { _this._mainContentContainer = jsonString; + + if (lists != null) _this._listDefinitions = new ListDefinitionsOrigin(lists); } else { - //the original version only accepts a string as a constructor, but this is javascript and it's almost easier to get a JSON value than a string, so we're silently accepting btoh + //the original version only accepts a string as a constructor, but this is javascript and it's almost easier to get a JSON value than a string, so we're silently accepting both var rootObject = typeof jsonString === 'string' ? JSON.parse(jsonString) : jsonString; var versionObj = rootObject["inkVersion"]; @@ -3891,6 +4943,11 @@ var rootToken = rootObject["root"]; if (rootToken == null) throw "Root node for ink not found. Are you sure it's a valid .ink.json file?"; + var listDefsObj; + if (listDefsObj = rootObject["listDefs"]) { + _this._listDefinitions = JsonSerialisation.JTokenToListDefinitions(listDefsObj); + } + _this._mainContentContainer = JsonSerialisation.JTokenToRuntimeObject(rootToken); _this._hasValidatedExternals = null; @@ -3910,6 +4967,8 @@ rootObject["inkVersion"] = this.inkVersionCurrent; rootObject["root"] = rootContainerJsonList; + if (this._listDefinitions != null) rootObject["listDefs"] = JsonSerialisation.ListDefinitionsToJToken(this._listDefinitions); + return JSON.stringify(rootObject); } }, { @@ -3979,7 +5038,7 @@ // which are actually built out of text content. // So we have to take a snapshot of the state, continue prospectively, // and rewind if necessary. - // This code is slightly fragile :-/ + // This code is slightly fragile :-/ // do { @@ -4030,7 +5089,13 @@ // We're going to continue stepping in case we see glue or some // non-text content such as choices. if (this.canContinue) { - stateAtLastNewline = this.StateSnapshot(); + // Don't bother to record the state beyond the current newline. + // e.g.: + // Hello world\n // record state at the end of here + // ~ complexCalculation() // don't actually need this unless it generates text + if (stateAtLastNewline == null) { + stateAtLastNewline = this.StateSnapshot(); + } } // Can't continue, so we're about to exit - make sure we @@ -4054,7 +5119,7 @@ this.Error("Thread available to pop, threads should always be flat by the end of evaluation?"); } - if (this.currentChoices.length == 0 && !this.state.didSafeExit && this._temporaryEvaluationContainer == null) { + if (this.state.generatedChoices.length == 0 && !this.state.didSafeExit && this._temporaryEvaluationContainer == null) { if (this.state.callStack.CanPop(PushPopType.Tunnel)) { this.Error("unexpectedly reached end of content. Do you need a '->->' to return from a tunnel?"); } else if (this.state.callStack.CanPop(PushPopType.Function)) { @@ -4155,7 +5220,7 @@ if (choicePoint instanceof ChoicePoint) { var choice = this.ProcessChoice(choicePoint); if (choice) { - this.state.currentChoices.push(choice); + this.state.generatedChoices.push(choice); } currentContentObj = null; @@ -4222,12 +5287,12 @@ if (!newContentObject) return; // First, find the previously open set of containers - var prevContainerSet = []; + if (this._prevContainerSet == null) this._prevContainerSet = []; if (previousContentObject) { // Container prevAncestor = previousContentObject as Container ?? previousContentObject.parent as Container; var prevAncestor = previousContentObject instanceof Container ? previousContentObject : previousContentObject.parent; while (prevAncestor instanceof Container) { - prevContainerSet.push(prevAncestor); + this._prevContainerSet.push(prevAncestor); // prevAncestor = prevAncestor.parent as Container; prevAncestor = prevAncestor.parent; } @@ -4238,7 +5303,7 @@ var currentChildOfContainer = newContentObject; // Container currentContainerAncestor = currentChildOfContainer.parent as Container; var currentContainerAncestor = currentChildOfContainer.parent; - while (currentContainerAncestor instanceof Container && prevContainerSet.indexOf(currentContainerAncestor) < 0) { + while (currentContainerAncestor instanceof Container && this._prevContainerSet.indexOf(currentContainerAncestor) < 0) { // Check whether this ancestor container is being entered at the start, // by checking whether the child object is the first. @@ -4435,22 +5500,38 @@ var popType = evalCommand.commandType == ControlCommand.CommandType.PopFunction ? PushPopType.Function : PushPopType.Tunnel; - if (this.state.callStack.currentElement.type != popType || !this.state.callStack.canPop) { + var overrideTunnelReturnTarget = null; + if (popType == PushPopType.Tunnel) { + var popped = this.state.PopEvaluationStack(); + // overrideTunnelReturnTarget = popped as DivertTargetValue; + overrideTunnelReturnTarget = popped; + if (overrideTunnelReturnTarget instanceof DivertTargetValue === false) { + if (popped instanceof Void === false) { + throw "Expected void if ->-> doesn't override target"; + } else { + overrideTunnelReturnTarget = null; + } + } + } + + if (this.state.TryExitExternalFunctionEvaluation()) { + break; + } else if (this.state.callStack.currentElement.type != popType || !this.state.callStack.canPop) { - var names = new {}(); + var names = {}; names[PushPopType.Function] = "function return statement (~ return)"; names[PushPopType.Tunnel] = "tunnel onwards statement (->->)"; var expected = names[this.state.callStack.currentElement.type]; - if (!this.state.callStack.canPop) { - expected = "end of flow (-> END or choice)"; - } + if (!this.state.callStack.canPop) expected = "end of flow (-> END or choice)"; var errorMsg = "Found " + names[popType] + ", when expected " + expected; this.Error(errorMsg); } else { this.state.callStack.Pop(); + + if (overrideTunnelReturnTarget) this.state.divertedTargetObject = this.ContentAtPath(overrideTunnelReturnTarget.targetPath); } break; @@ -4498,7 +5579,7 @@ break; case ControlCommand.CommandType.ChoiceCount: - var choiceCount = this.currentChoices.length; + var choiceCount = this.state.generatedChoices.length; this.state.PushEvaluationStack(new IntValue(choiceCount)); break; @@ -4592,6 +5673,79 @@ this.state.ForceEnd(); break; + case ControlCommand.CommandType.ListFromInt: + // var intVal = state.PopEvaluationStack () as IntValue; + var intVal = parseInt(this.state.PopEvaluationStack()); + // var listNameVal = state.PopEvaluationStack () as StringValue; + var listNameVal = this.state.PopEvaluationStack().toString(); + + var generatedListValue = null; + + var foundListDef; + if (foundListDef = this.listDefinitions.TryGetDefinition(listNameVal.value, foundListDef)) { + var foundItem = foundListDef.TryGetItemWithValue(intVal.value); + if (foundItem.exists) { + generatedListValue = new ListValue(foundItem.item, intVal.value); + } + } else { + throw new StoryException("Failed to find LIST called " + listNameVal.value); + } + + if (generatedListValue == null) generatedListValue = new ListValue(); + + this.state.PushEvaluationStack(generatedListValue); + break; + + case ControlCommand.CommandType.ListRange: + var max = this.state.PopEvaluationStack(); + var min = this.state.PopEvaluationStack(); + + // var targetList = state.PopEvaluationStack () as ListValue; + var targetList = this.state.PopEvaluationStack(); + + if (targetList instanceof ListValue === false || targetList == null || min == null || max == null) throw new StoryException("Expected list, minimum and maximum for LIST_RANGE"); + + // Allow either int or a particular list item to be passed for the bounds, + // so wrap up a function to handle this casting for us. + + var IntBound = function IntBound(obj) { + // var listValue = obj as ListValue; + var listValue = obj; + if (listValue instanceof ListValue) { + return parseInt(listValue.value.maxItem.Value); + } + + // var intValue = obj as IntValue; + var intValue = obj; + if (intValue instanceof IntValue) { + return intValue.value; + } + + return -1; + }; + + var minVal = IntBound(min); + var maxVal = IntBound(max); + if (minVal == -1) throw new StoryException("Invalid min range bound passed to LIST_VALUE(): " + min); + + if (maxVal == -1) throw new StoryException("Invalid max range bound passed to LIST_VALUE(): " + max); + + // Extract the range of items from the origin list + var result = new ListValue(); + var origins = targetList.value.origins; + + if (origins != null) { + origins.forEach(function (origin) { + var rangeFromOrigin = origin.ListRange(minVal, maxVal); + rangeFromOrigin.value.forEach(function (kv) { + result.value.Add(kv.Key, kv.Value); + }); + }); + } + + this.state.PushEvaluationStack(result); + break; + default: this.Error("unhandled ControlCommand: " + evalCommand); break; @@ -4638,7 +5792,7 @@ } } - this.state.evaluationStack.push(foundValue); + this.state.PushEvaluationStack(foundValue); return true; } @@ -4648,7 +5802,7 @@ var func = contentObj; var funcParams = this.state.PopEvaluationStack(func.numberOfParameters); var result = func.Call(funcParams); - this.state.evaluationStack.push(result); + this.state.PushEvaluationStack(result); return true; } @@ -4675,7 +5829,7 @@ var choices = this.currentChoices; if (choiceIdx < 0 || choiceIdx > choices.length) console.warn("choice out of range"); - // Replace callstack with the one from the thread at the choosing point, + // Replace callstack with the one from the thread at the choosing point, // so that we can jump into the right place in the flow. // This is important in case the flow was forked by a new thread, which // can create multiple leading edges for the story, each of @@ -4713,34 +5867,7 @@ if (e.message.indexOf("not found") >= 0) throw "Function doesn't exist: '" + functionName + "'";else throw e; } - // We'll start a new callstack, so keep hold of the original, - // as well as the evaluation stack so we know if the function - // returned something - var originalCallstack = this.state.callStack; - var originalEvaluationStackHeight = this.state.evaluationStack.length; - - // Create a new base call stack element. - // By making it point at element 0 of the base, when NextContent is - // called, it'll actually step past the entire content of the game (!) - // and straight onto the Done. Bit of a hack :-/ We don't really have - // a better way of creating a temporary context that ends correctly. - this.state.callStack = new CallStack(this.mainContentContainer); - this.state.callStack.currentElement.currentContainer = this.mainContentContainer; - this.state.callStack.currentElement.currentContentIndex = 0; - - if (args != null) { - 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.push(Value.Create(args[i])); - } - } - - // Jump into the function! - this.state.callStack.Push(PushPopType.Function); - this.state.currentContentObject = funcContainer; + this.state.StartExternalFunctionEvaluation(funcContainer, args); // Evaluate the function, and collect the string output var stringOutput = new StringBuilder(); @@ -4749,48 +5876,16 @@ } var textOutput = stringOutput.toString(); - // Restore original stack - this.state.callStack = originalCallstack; - - // Do we have a returned value? - // Potentially pop multiple values off the stack, in case we need - // to clean up after ourselves (e.g. caller of EvaluateFunction may - // have passed too many arguments, and we currently have no way to check for that) - var returnedObj = null; - while (this.state.evaluationStack.length > originalEvaluationStackHeight) { - var poppedObj = this.state.PopEvaluationStack(); - 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) returnedValue = null; - - // Some kind of value, if not void - // var returnVal = returnedObj as Runtime.Value; - var returnVal = returnedObj; - - // DivertTargets get returned as the string of components - // (rather than a Path, which isn't public) - if (returnVal.valueType == ValueType.DivertTarget) { - returnedValue = returnVal.valueObject.toString(); - } - - // Other types can just have their exact object type: - // int, float, string. VariablePointers get returned as strings. - returnedValue = returnVal.valueObject; - } + var result = this.state.CompleteExternalFunctionEvaluation(); - return returnTextOutput ? { 'returned': returnedValue, 'output': textOutput } : returnedValue; + return returnTextOutput ? { 'returned': result, 'output': textOutput } : result; } }, { key: 'EvaluateExpression', value: function EvaluateExpression(exprContainer) { var startCallStackHeight = this.state.callStack.elements.length; - this.state.callStack.push(PushPopType.Tunnel); + this.state.callStack.Push(PushPopType.Tunnel); this._temporaryEvaluationContainer = exprContainer; @@ -4832,7 +5927,7 @@ if (!(fallbackFunctionContainer instanceof Container)) console.warn("Trying to call EXTERNAL function '" + funcName + "' which has not been bound, and fallback ink function could not be found."); // Divert direct into fallback function and we're done - this.state.callStack.push(PushPopType.Function); + this.state.callStack.Push(PushPopType.Function); this.state.divertedTargetObject = fallbackFunctionContainer; return; } else { @@ -4904,27 +5999,45 @@ } }, { key: 'ValidateExternalBindings', - value: function ValidateExternalBindings(containerOrObject) { + value: function ValidateExternalBindings(containerOrObject, missingExternals) { var _this3 = this; if (!containerOrObject) { - this.ValidateExternalBindings(this._mainContentContainer); + var missingExternals = []; + this.ValidateExternalBindings(this._mainContentContainer, missingExternals); this._hasValidatedExternals = true; + + // No problem! Validation complete + if (missingExternals.length == 0) { + this._hasValidatedExternals = true; + } + + // Error for all missing externals + else { + var message = "Error: Missing function binding for external"; + message += missingExternals.length > 1 ? "s" : ""; + message += ": '"; + message += missingExternals.join("', '"); + message += "' "; + message += this.allowExternalFunctionFallbacks ? ", and no fallback ink function found." : " (ink fallbacks disabled)"; + + this.Error(message); + } } else if (containerOrObject instanceof Container) { var c = containerOrObject; c.content.forEach(function (innerContent) { - _this3.ValidateExternalBindings(innerContent); + _this3.ValidateExternalBindings(innerContent, missingExternals); }); for (var key in c.namedContent) { - this.ValidateExternalBindings(c.namedContent[key]); + this.ValidateExternalBindings(c.namedContent[key], missingExternals); } } else { var o = containerOrObject; //the following code is already taken care of above in this implementation // var container = o as Container; // if (container) { - // ValidateExternalBindings (container); + // ValidateExternalBindings (container, missingExternals); // return; // } @@ -4934,20 +6047,13 @@ var name = divert.targetPathString; if (!this._externals[name]) { - - var fallbackFunction = this.mainContentContainer.namedContent[name]; - var fallbackFound = typeof fallbackFunction !== 'undefined'; - - var message = null; - if (!this.allowExternalFunctionFallbacks) message = "Missing function binding for external '" + name + "' (ink fallbacks disabled)";else if (!fallbackFound) { - message = "Missing function binding for external '" + name + "', and no fallback ink function found."; - } - - if (message != null) { - var errorPreamble = "ERROR: "; - //misses a bit about metadata, which isn't implemented - - throw new StoryException(errorPreamble + message); + if (this.allowExternalFunctionFallbacks) { + var fallbackFound = !!this.mainContentContainer.namedContent[name]; + if (!fallbackFound) { + missingExternals.push(name); + } + } else { + missingExternals.push(name); } } } @@ -5022,15 +6128,15 @@ // 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]; + while (true) { + var firstContent = flowContainer.content[0]; + if (firstContent instanceof Container) flowContainer = firstContent;else break; + } // Any initial tag objects count as the "main tags" associated with that story/knot/stitch var tags = null; - innerWeaveContainer.content.every(function (c) { + flowContainer.content.every(function (c) { // var tag = c as Runtime.Tag; var tag = c; if (tag instanceof Tag) { @@ -5101,6 +6207,8 @@ this.state.callStack.PopThread(); didPop = true; + } else { + this.state.TryExitExternalFunctionEvaluation(); } // Step past the point where we last called out @@ -5318,6 +6426,11 @@ get: function get() { return this.state.variablesState; } + }, { + key: 'listDefinitions', + get: function get() { + return this._listDefinitions; + } }, { key: 'state', get: function get() { @@ -5335,7 +6448,7 @@ }, { key: 'canContinue', get: function get() { - return this.state.currentContentObject != null && !this.state.hasError; + return this.state.canContinue; } }, { key: 'globalTags', @@ -5344,8 +6457,10 @@ } }]); return Story; - }(InkObject); + }(Object$1); exports.Story = Story; -})); \ No newline at end of file + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/templates/browser_with_server/ink.js b/templates/browser_with_server/ink.js index 65f976dd..cce524b7 100644 --- a/templates/browser_with_server/ink.js +++ b/templates/browser_with_server/ink.js @@ -2,78 +2,7 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define('inkjs', ['exports'], factory) : (factory((global.inkjs = global.inkjs || {}))); -}(this, function (exports) { 'use strict'; - - var babelHelpers = {}; - babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; - }; - - babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - }; - - babelHelpers.createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - - babelHelpers.extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; - }; - - babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - }; - - babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return call && (typeof call === "object" || typeof call === "function") ? call : self; - }; - - babelHelpers; +}(this, (function (exports) { 'use strict'; var Path$1 = function () { function Path() /*polymorphic constructor*/{ @@ -218,7 +147,10 @@ var componentStrings = componentsStr.split('.'); componentStrings.forEach(function (str) { - if (!isNaN(parseInt(str))) { + //we need to distinguish between named components that start with a number, eg "42somewhere", and indexed components + //the normal parseInt won't do for the detection because it's too relaxed. + //see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt + if (/^(\-|\+)?([0-9]+|Infinity)$/.test(str)) { _this.components.push(new Component(parseInt(str))); } else { _this.components.push(new Component(str)); @@ -303,7 +235,7 @@ Path$1.parentId = "^"; Path$1.Component = Component; - var InkObject = function () { + var Object$1 = function () { function Object() { babelHelpers.classCallCheck(this, Object); @@ -448,15 +380,405 @@ return Object; }(); + var StringBuilder = function () { + function StringBuilder(str) { + babelHelpers.classCallCheck(this, StringBuilder); + + str = typeof str !== 'undefined' ? str.toString() : ''; + this._string = str; + } + + babelHelpers.createClass(StringBuilder, [{ + key: 'Append', + value: function Append(str) { + this._string += str; + } + }, { + key: 'AppendLine', + value: function AppendLine(str) { + if (typeof str !== 'undefined') this.Append(str); + this._string += "\n"; + } + }, { + key: 'AppendFormat', + value: function 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; + }); + } + }, { + key: 'toString', + value: function toString() { + return this._string; + } + }, { + key: 'Length', + get: function get() { + return this._string.length; + } + }]); + return StringBuilder; + }(); + + var RawListItem = function () { + function RawListItem(fullNameOrOriginName, itemName) { + babelHelpers.classCallCheck(this, RawListItem); + + if (itemName !== undefined) { + this.originName = fullNameOrOriginName; + this.itemName = itemName; + } else { + var nameParts = fullNameOrOriginName.toString().split('.'); + this.originName = nameParts[0]; + this.itemName = nameParts[1]; + } + } + + babelHelpers.createClass(RawListItem, [{ + key: 'isNull', + value: function isNull() { + return this.originName == null && this.itemName == null; + } + }, { + key: 'toString', + value: function toString() { + return this.fullname; + } + }, { + key: 'Equals', + value: function Equals(obj) { + if (obj instanceof RawListItem) { + // var otherItem = (RawListItem)obj; + var otherItem = obj; + return otherItem.itemName == this.itemName && otherItem.originName == this.originName; + } + + return false; + } + //GetHashCode not implemented + + }, { + key: 'toString', + value: function toString() { + //WARNING: experimental. RawListItem are structs and are used as keys inside hashes. In js, we can't use an object as a key, as the key needs to be a string. C# gets around that with the internal GetHashCode, and the js equivalent to that is toString. So here, toString acts as C#'s GetHashCode + var originCode = '0'; + var itemCode = this.itemName ? this.itemName.toString() : 'null'; + if (this.originName != null) originCode = this.originName.toString(); + + return originCode + itemCode; + } + }, { + key: 'fullName', + get: function get() { + return (this.originName !== null ? this.originName : "?") + "." + this.itemName; + } + }], [{ + key: 'Null', + value: function Null() { + return new RawListItem(null, null); + } + }]); + return RawListItem; + }(); + + //in C#, rawlists are based on dictionnary; the equivalent of a dictionnary in js is Object, but we can't use that or it will conflate dictionnary items and RawList class properties. + //instead RawList-js has a special _values property wich contains the actual "Dictionnary", and a few Dictionnary methods are re-implemented on RawList. This also means directly iterating over the RawList won't work as expected. Maybe we can return a proxy if that's required. + var RawList = function () { + function RawList(otherListOrSingleElement) { + var _this = this; + + babelHelpers.classCallCheck(this, RawList); + + this._keys = {}; + this._values = {}; + this.origins = null; + this._originNames = null; + + //polymorphioc constructor + if (otherListOrSingleElement) { + if (otherListOrSingleElement instanceof RawList) { + var otherList = otherListOrSingleElement; + otherList.forEach(function (kv) { + _this.Add(kv.Key, kv.Value); + }); + + this._originNames = otherList._originNames; + } else if (otherListOrSingleElement.hasOwnProperty('Key') && otherListOrSingleElement.hasOwnProperty('Value')) { + var singleElement = otherListOrSingleElement; + this.Add(singleElement.Key, singleElement.Value); + } + } + } + + babelHelpers.createClass(RawList, [{ + key: 'forEach', + value: function forEach(fn) { + for (var key in this._values) { + fn({ + Key: this._keys[key], + Value: this._values[key] + }); + } + } + }, { + key: 'ContainsKey', + value: function ContainsKey(key) { + return key in this._values; + } + }, { + key: 'Add', + value: function Add(key, value) { + this._keys[key] = key; + this._values[key] = value; + } + }, { + key: 'Remove', + value: function Remove(key) { + delete this._values[key]; + delete this._keys[key]; + } + }, { + key: 'SetInitialOriginName', + value: function SetInitialOriginName(initialOriginName) { + this._originNames = [initialOriginName]; + } + }, { + key: 'SetInitialOriginNames', + value: function SetInitialOriginNames(initialOriginNames) { + this._originNames = initialOriginNames.slice(); //store a copy + } + }, { + key: 'Union', + value: function Union(otherList) { + var union = new RawList(this); + otherList.forEach(function (kv) { + union.Add(kv.Key, kv.Value); + }); + return union; + } + }, { + key: 'Intersect', + value: function Intersect(otherList) { + var intersection = new RawList(); + this.forEach(function (kv) { + if (otherList.ContainsKey(kv.Key)) intersection.Add(kv.Key, kv.Value); + }); + return intersection; + } + }, { + key: 'Without', + value: function Without(listToRemove) { + var result = new RawList(this); + listToRemove.forEach(function (kv) { + result.Remove(kv.Key); + }); + return result; + } + }, { + key: 'Contains', + value: function Contains(otherList) { + var _this2 = this; + + var contains = true; + otherList.forEach(function (kv) { + if (!_this2.ContainsKey(kv.Key)) contains = false; + }); + return contains; + } + }, { + key: 'GreaterThan', + value: function GreaterThan(otherList) { + if (this.Count == 0) return false; + if (otherList.Count == 0) return true; + + // All greater + return this.minItem.Value > otherList.maxItem.Value; + } + }, { + key: 'GreaterThanOrEquals', + value: function GreaterThanOrEquals(otherList) { + if (this.Count == 0) return false; + if (otherList.Count == 0) return true; + + return this.minItem.Value >= otherList.minItem.Value && this.maxItem.Value >= otherList.maxItem.Value; + } + }, { + key: 'LessThan', + value: function LessThan(otherList) { + if (otherList.Count == 0) return false; + if (this.Count == 0) return true; + + return this.maxItem.Value < otherList.minItem.Value; + } + }, { + key: 'LessThanOrEquals', + value: function LessThanOrEquals(otherList) { + if (otherList.Count == 0) return false; + if (this.Count == 0) return true; + + return this.maxItem.Value <= otherList.maxItem.Value && this.minItem.Value <= otherList.minItem.Value; + } + }, { + key: 'MaxAsList', + value: function MaxAsList() { + if (this.Count > 0) return new RawList(this.maxItem);else return new RawList(); + } + }, { + key: 'MinAsList', + value: function MinAsList() { + if (this.Count > 0) return new RawList(this.minItem);else return new RawList(); + } + }, { + key: 'Equals', + value: function Equals(other) { + // var otherRawList = other as RawList; + var otherRawList = other; + if (otherRawList instanceof RawList === false) return false; + if (otherRawList.Count != this.Count) return false; + + var equals = true; + this.forEach(function (kv) { + if (!otherRawList.ContainsKey(kv.Key)) equals = false; + }); + + return equals; + } + //GetHashCode not implemented + + }, { + key: 'toString', + value: function toString() { + var ordered = []; + this.forEach(function (kv) { + ordered.push(kv); + }); + ordered = ordered.sort(function (a, b) { + return a.Value === b.Value ? 0 : a.Value > b.Value ? 1 : -1; + }); + + var sb = new StringBuilder(); + for (var i = 0; i < ordered.length; i++) { + if (i > 0) sb.Append(", "); + + var item = ordered[i].Key; + sb.Append(item.itemName); + } + + return sb.toString(); + } + //casting a RawList to a Number, for somereason, actually gives a number. This messes up the type detection when creating a Value from a RawList. Returning NaN here prevents that. + + }, { + key: 'valueOf', + value: function valueOf() { + return NaN; + } + }, { + key: 'Count', + get: function get() { + return Object.keys(this._values).length; + } + }, { + key: 'originOfMaxItem', + get: function get() { + if (this.origins == null) return null; + + var maxOriginName = this.maxItem.Key.originName; + var result = null; + this.origins.every(function (origin) { + if (origin.name == maxOriginName) { + result = origin; + return false; + } else return true; + }); + + return result; + } + }, { + key: 'originNames', + get: function get() { + var _this3 = this; + + if (this.Count > 0) { + if (this._originNames == null && this.Count > 0) this._originNames = [];else this._originNames.length = 0; + + this.forEach(function (itemAndValue) { + _this3._originNames.push(itemAndValue.Key.originName); + }); + } + + return this._originNames; + } + }, { + key: 'maxItem', + get: function get() { + var max = { + Key: null, + Value: null + }; + this.forEach(function (kv) { + if (max.Key === null || kv.Value > max.Value) max = kv; + }); + + return max; + } + }, { + key: 'minItem', + get: function get() { + var min = { + Key: null, + Value: null + }; + this.forEach(function (kv) { + if (min.Key === null || kv.Value < min.Value) min = kv; + }); + + return min; + } + }, { + key: 'inverse', + get: function get() { + var _this4 = this; + + var list = new RawList(); + if (this.origins != null) { + this.origins.forEach(function (origin) { + origin.items.forEach(function (itemAndValue) { + if (!_this4.ContainsKey(itemAndValue.Key)) list.Add(itemAndValue.Key, itemAndValue.Value); + }); + }); + } + return list; + } + }, { + key: 'all', + get: function get() { + var list = new RawList(); + if (this.origins != null) { + this.origins.forEach(function (origin) { + origin.items.forEach(function (itemAndValue) { + list.Add(itemAndValue.Key, itemAndValue.Value); + }); + }); + } + return list; + } + }]); + return RawList; + }(); + var ValueType = { // Used in coersion Int: 0, Float: 1, - String: 2, + List: 2, + String: 3, // Not used for coersion described above - DivertTarget: 3, - VariablePointer: 4 + DivertTarget: 4, + VariablePointer: 5 }; var AbstractValue = function (_InkObject) { @@ -465,7 +787,7 @@ function AbstractValue(val) { babelHelpers.classCallCheck(this, AbstractValue); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(AbstractValue).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (AbstractValue.__proto__ || Object.getPrototypeOf(AbstractValue)).call(this)); _this._valueType; _this._isTruthy; @@ -481,7 +803,7 @@ }, { key: 'Copy', value: function Copy(val) { - return this.Create(val); + return AbstractValue.Create(val); } }, { key: 'valueType', @@ -515,13 +837,15 @@ return new StringValue(val); } else if (val instanceof Path$1) { return new DivertTargetValue(val); + } else if (val instanceof RawList) { + return new ListValue(val); } return null; } }]); return AbstractValue; - }(InkObject); + }(Object$1); var Value = function (_AbstractValue) { babelHelpers.inherits(Value, _AbstractValue); @@ -529,7 +853,7 @@ function Value(val) { babelHelpers.classCallCheck(this, Value); - var _this2 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Value).call(this)); + var _this2 = babelHelpers.possibleConstructorReturn(this, (Value.__proto__ || Object.getPrototypeOf(Value)).call(this)); _this2.value = val; return _this2; @@ -563,7 +887,7 @@ function IntValue(val) { babelHelpers.classCallCheck(this, IntValue); - var _this3 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(IntValue).call(this, val || 0)); + var _this3 = babelHelpers.possibleConstructorReturn(this, (IntValue.__proto__ || Object.getPrototypeOf(IntValue)).call(this, val || 0)); _this3._valueType = ValueType.Int; return _this3; @@ -606,7 +930,7 @@ function FloatValue(val) { babelHelpers.classCallCheck(this, FloatValue); - var _this4 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(FloatValue).call(this, val || 0.0)); + var _this4 = babelHelpers.possibleConstructorReturn(this, (FloatValue.__proto__ || Object.getPrototypeOf(FloatValue)).call(this, val || 0.0)); _this4._valueType = ValueType.Float; return _this4; @@ -649,7 +973,7 @@ function StringValue(val) { babelHelpers.classCallCheck(this, StringValue); - var _this5 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(StringValue).call(this, val || '')); + var _this5 = babelHelpers.possibleConstructorReturn(this, (StringValue.__proto__ || Object.getPrototypeOf(StringValue)).call(this, val || '')); _this5._valueType = ValueType.String; @@ -730,7 +1054,7 @@ function DivertTargetValue(targetPath) { babelHelpers.classCallCheck(this, DivertTargetValue); - var _this6 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(DivertTargetValue).call(this, targetPath)); + var _this6 = babelHelpers.possibleConstructorReturn(this, (DivertTargetValue.__proto__ || Object.getPrototypeOf(DivertTargetValue)).call(this, targetPath)); _this6._valueType = ValueType.DivertTarget; return _this6; @@ -771,7 +1095,7 @@ function VariablePointerValue(variableName, contextIndex) { babelHelpers.classCallCheck(this, VariablePointerValue); - var _this7 = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(VariablePointerValue).call(this, variableName)); + var _this7 = babelHelpers.possibleConstructorReturn(this, (VariablePointerValue.__proto__ || Object.getPrototypeOf(VariablePointerValue)).call(this, variableName)); _this7._valueType = ValueType.VariablePointer; _this7.contextIndex = typeof contextIndex !== 'undefined' ? contextIndex : -1; @@ -812,13 +1136,87 @@ return VariablePointerValue; }(Value); + var ListValue = function (_Value6) { + babelHelpers.inherits(ListValue, _Value6); + babelHelpers.createClass(ListValue, [{ + key: 'Cast', + value: function Cast(newType) { + if (newType == ValueType.Int) { + var max = this.value.maxItem; + if (max.Key.isNull) return new IntValue(0);else return new IntValue(max.Value); + } else if (newType == ValueType.Float) { + var max = this.value.maxItem; + if (max.Key.isNull) return new FloatValue(0.0);else return new FloatValue(parseFloat(max.Value)); + } else if (newType == ValueType.String) { + var max = value.maxItem; + if (max.Key.isNull) return new StringValue("");else { + return new StringValue(max.Key.toString()); + } + } + + if (newType == this.valueType) return this; + + throw "Unexpected type cast of Value to new ValueType"; + } + }, { + key: 'valueType', + get: function get() { + return ValueType.List; + } + }, { + key: 'isTruthy', + get: function get() { + var isTruthy = false; + this.value.forEach(function (kv) { + var listItemIntValue = kv.Value; + if (listItemIntValue != 0) isTruthy = true; + }); + return isTruthy; + } + }]); + + function ListValue(listOrSingleItem, singleValue) { + babelHelpers.classCallCheck(this, ListValue); + + var _this8 = babelHelpers.possibleConstructorReturn(this, (ListValue.__proto__ || Object.getPrototypeOf(ListValue)).call(this, null)); + + _this8._valueType = ValueType.List; + + if (listOrSingleItem instanceof RawList) { + _this8.value = new RawList(listOrSingleItem); + } else if (listOrSingleItem !== undefined && singleValue !== undefined) { + _this8.value = new RawList({ + Key: listOrSingleItem, + Value: singleValue + }); + } else { + _this8.value = new RawList(); + } + return _this8; + } + + babelHelpers.createClass(ListValue, null, [{ + key: 'RetainListOriginsForAssignment', + value: function RetainListOriginsForAssignment(oldValue, newValue) { + // var oldList = oldValue as ListValue; + var oldList = oldValue; + // var newList = newValue as ListValue; + var newList = newValue; + + // When assigning the emtpy list, try to retain any initial origin names + if (oldList instanceof ListValue && newList instanceof ListValue && newList.value.Count == 0) newList.value.SetInitialOriginNames(oldList.value.originNames); + } + }]); + return ListValue; + }(Value); + var StoryException = function (_Error) { babelHelpers.inherits(StoryException, _Error); function StoryException(message) { babelHelpers.classCallCheck(this, StoryException); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(StoryException).call(this, message)); + var _this = babelHelpers.possibleConstructorReturn(this, (StoryException.__proto__ || Object.getPrototypeOf(StoryException)).call(this, message)); _this.message = message; _this.name = 'StoryException'; @@ -828,56 +1226,14 @@ return StoryException; }(Error); - var StringBuilder = function () { - function StringBuilder(str) { - babelHelpers.classCallCheck(this, StringBuilder); - - str = typeof str !== 'undefined' ? str.toString() : ''; - this._string = str; - } - - babelHelpers.createClass(StringBuilder, [{ - key: 'Append', - value: function Append(str) { - this._string += str; - } - }, { - key: 'AppendLine', - value: function AppendLine(str) { - if (typeof str !== 'undefined') this.Append(str); - this._string += "\n"; - } - }, { - key: 'AppendFormat', - value: function 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; - }); - } - }, { - key: 'toString', - value: function toString() { - return this._string; - } - }, { - key: 'Length', - get: function get() { - return this._string.length; - } - }]); - return StringBuilder; - }(); - var Container = function (_InkObject) { babelHelpers.inherits(Container, _InkObject); - //also implements INamedContent. Not sure how to do it cleanly in JS. + //also implements INamedContent. Not sure how to do it cleanly in JS. function Container() { babelHelpers.classCallCheck(this, Container); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Container).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (Container.__proto__ || Object.getPrototypeOf(Container)).call(this)); _this.name = ''; @@ -931,7 +1287,7 @@ }, { key: 'AddToNamedContentOnly', value: function AddToNamedContentOnly(namedContentObj) { - if (namedContentObj instanceof InkObject === false) console.warn("Can only add Runtime.Objects to a Runtime.Container"); + if (namedContentObj instanceof Object$1 === false) console.warn("Can only add Runtime.Objects to a Runtime.Container"); namedContentObj.parent = this; this.namedContent[namedContentObj.name] = namedContentObj; @@ -1193,13 +1549,18 @@ } }]); return Container; - }(InkObject); + }(Object$1); + + var Glue = function (_InkObject) { + babelHelpers.inherits(Glue, _InkObject); - var Glue = function () { function Glue(type) { babelHelpers.classCallCheck(this, Glue); - this.glueType = type; + var _this = babelHelpers.possibleConstructorReturn(this, (Glue.__proto__ || Object.getPrototypeOf(Glue)).call(this)); + + _this.glueType = type; + return _this; } babelHelpers.createClass(Glue, [{ @@ -1233,7 +1594,7 @@ } }]); return Glue; - }(); + }(Object$1); var GlueType = { Bidirectional: 0, @@ -1247,7 +1608,7 @@ function ControlCommand(commandType) { babelHelpers.classCallCheck(this, ControlCommand); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(ControlCommand).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (ControlCommand.__proto__ || Object.getPrototypeOf(ControlCommand)).call(this)); _this._commandType = typeof commandType != 'undefined' ? commandType : CommandType.NotSet; return _this; @@ -1363,9 +1724,19 @@ value: function End() { return new ControlCommand(CommandType.End); } + }, { + key: 'ListFromInt', + value: function ListFromInt() { + return new ControlCommand(CommandType.ListFromInt); + } + }, { + key: 'ListRange', + value: function ListRange() { + return new ControlCommand(CommandType.ListRange); + } }]); return ControlCommand; - }(InkObject); + }(Object$1); var CommandType = { NotSet: -1, @@ -1387,7 +1758,9 @@ SequenceShuffleIndex: 15, StartThread: 16, Done: 17, - End: 18 + End: 18, + ListFromInt: 19, + ListRange: 20 }; CommandType.TOTAL_VALUES = Object.keys(CommandType).length - 1; //-1 because NotSet shoudn't count ControlCommand.CommandType = CommandType; @@ -1403,7 +1776,7 @@ function Divert(stackPushType) { babelHelpers.classCallCheck(this, Divert); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Divert).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (Divert.__proto__ || Object.getPrototypeOf(Divert)).call(this)); _this._targetPath; _this._targetContent; @@ -1522,7 +1895,7 @@ } }]); return Divert; - }(InkObject); + }(Object$1); var ChoicePoint = function (_InkObject) { babelHelpers.inherits(ChoicePoint, _InkObject); @@ -1530,9 +1903,9 @@ function ChoicePoint(onceOnly) { babelHelpers.classCallCheck(this, ChoicePoint); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(ChoicePoint).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (ChoicePoint.__proto__ || Object.getPrototypeOf(ChoicePoint)).call(this)); - _this.pathOnChoice; + _this._pathOnChoice; _this.hasCondition; _this.hasStartContent; _this.hasChoiceOnlyContent; @@ -1556,11 +1929,25 @@ return "Choice: -> " + targetString; } + }, { + key: 'pathOnChoice', + get: function get() { + if (this._pathOnChoice != null && this._pathOnChoice.isRelative) { + var choiceTargetObj = this.choiceTarget; + if (choiceTargetObj) { + this._pathOnChoice = choiceTargetObj.path; + } + } + return this._pathOnChoice; + }, + set: function set(value) { + this._pathOnChoice = value; + } }, { key: 'choiceTarget', get: function get() { - //return this.ResolvePath (pathOnChoice) as Container; - return this.ResolvePath(this.pathOnChoice); + //return this.ResolvePath (_pathOnChoice) as Container; + return this.ResolvePath(this._pathOnChoice); } }, { key: 'pathStringOnChoice', @@ -1590,7 +1977,7 @@ } }]); return ChoicePoint; - }(InkObject); + }(Object$1); var VariableReference = function (_InkObject) { babelHelpers.inherits(VariableReference, _InkObject); @@ -1598,7 +1985,7 @@ function VariableReference(name) { babelHelpers.classCallCheck(this, VariableReference); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(VariableReference).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (VariableReference.__proto__ || Object.getPrototypeOf(VariableReference)).call(this)); _this.name = name; _this.pathForCount; @@ -1632,7 +2019,7 @@ } }]); return VariableReference; - }(InkObject); + }(Object$1); var VariableAssignment = function (_InkObject) { babelHelpers.inherits(VariableAssignment, _InkObject); @@ -1640,7 +2027,7 @@ function VariableAssignment(variableName, isNewDeclaration) { babelHelpers.classCallCheck(this, VariableAssignment); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(VariableAssignment).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (VariableAssignment.__proto__ || Object.getPrototypeOf(VariableAssignment)).call(this)); _this._variableName = variableName || null; _this._isNewDeclaration = !!isNewDeclaration; @@ -1651,7 +2038,7 @@ babelHelpers.createClass(VariableAssignment, [{ key: "toString", value: function toString() { - return "VarAssign to " + this.variableName;; + return "VarAssign to " + this.variableName; } }, { key: "variableName", @@ -1665,26 +2052,27 @@ } }]); return VariableAssignment; - }(InkObject); + }(Object$1); var Void = function (_InkObject) { babelHelpers.inherits(Void, _InkObject); function Void() { babelHelpers.classCallCheck(this, Void); - return babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Void).apply(this, arguments)); + return babelHelpers.possibleConstructorReturn(this, (Void.__proto__ || Object.getPrototypeOf(Void)).apply(this, arguments)); } return Void; - }(InkObject); + }(Object$1); + //misses delegates, probably the returns from function calls var NativeFunctionCall = function (_InkObject) { babelHelpers.inherits(NativeFunctionCall, _InkObject); function NativeFunctionCall(name) { babelHelpers.classCallCheck(this, NativeFunctionCall); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(NativeFunctionCall).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (NativeFunctionCall.__proto__ || Object.getPrototypeOf(NativeFunctionCall)).call(this)); _this.name = name; _this._numberOfParameters; @@ -1708,14 +2096,20 @@ throw "Unexpected number of parameters"; } + var hasList = false; parameters.forEach(function (p) { if (p instanceof Void) throw new StoryException("Attempting to perform operation on a void value. Did you forget to 'return' a value from a function you called here?"); + if (p instanceof ListValue) hasList = true; }); + if (parameters.length == 2 && hasList) { + return this.CallBinaryListOperation(parameters); + } + var coercedParams = this.CoerceValuesToSingleType(parameters); var coercedType = coercedParams[0].valueType; - //Originally CallType gets a type parameter taht is used to do some casting, but we can do without. + //Originally CallType gets a type parameter that is used to do some casting, but we can do without. if (coercedType == ValueType.Int) { return this.CallType(coercedParams); } else if (coercedType == ValueType.Float) { @@ -1724,6 +2118,8 @@ return this.CallType(coercedParams); } else if (coercedType == ValueType.DivertTarget) { return this.CallType(coercedParams); + } else if (coercedType == ValueType.List) { + return this.CallType(coercedParams); } return null; @@ -1742,7 +2138,7 @@ var opForTypeObj = this._operationFuncs[valType]; if (!opForTypeObj) { - throw new StoryException("Can not perform operation '" + this.name + "' on " + valType); + throw new StoryException("Cannot perform operation '" + this.name + "' on " + valType); } // Binary @@ -1772,11 +2168,73 @@ throw "Unexpected number of parameters to NativeFunctionCall: " + parametersOfSingleType.length; } } + }, { + key: 'CallBinaryListOperation', + value: function CallBinaryListOperation(parameters) { + // List-Int addition/subtraction returns a List (e.g. "alpha" + 1 = "beta") + if ((this.name == "+" || this.name == "-") && parameters[0] instanceof ListValue && parameters[1] instanceof IntValue) return this.CallListIncrementOperation(parameters); + + // var v1 = parameters [0] as Value; + var v1 = parameters[0]; + // var v2 = parameters [1] as Value; + var v2 = parameters[1]; + + // And/or with any other type requires coerscion to bool (int) + if ((this.name == "&&" || this.name == "||") && (v1.valueType != ValueType.List || v2.valueType != ValueType.List)) { + // var op = _operationFuncs [ValueType.Int] as BinaryOp; + var op = this._operationFuncs[ValueType.Int]; + var result = op(v1.isTruthy ? 1 : 0, v2.isTruthy ? 1 : 0); + return parseInt(result); + } + + // Normal (list • list) operation + if (v1.valueType == ValueType.List && v2.valueType == ValueType.List) return this.CallType([v1, v2]); + + throw new StoryException("Can not call use '" + this.name + "' operation on " + v1.valueType + " and " + v2.valueType); + } + }, { + key: 'CallListIncrementOperation', + value: function CallListIncrementOperation(listIntParams) { + var _this2 = this; + + var listVal = listIntParams[0]; + var intVal = listIntParams[1]; + + var resultRawList = new RawList(); + + listVal.value.forEach(function (listItemWithValue) { + var listItem = listItemWithValue.Key; + var listItemValue = listItemWithValue.Value; + + // Find + or - operation + var intOp = _this2._operationFuncs[ValueType.Int]; + + // Return value unknown until it's evaluated + var targetInt = intOp(listItemValue, intVal.value); + + // Find this item's origin (linear search should be ok, should be short haha) + var itemOrigin = null; + listVal.value.origins.forEach(function (origin) { + if (origin.name == listItem.originName) { + itemOrigin = origin; + return false; + } + }); + if (itemOrigin != null) { + var incrementedItem = itemOrigin.TryGetItemWithValue(targetInt); + if (incrementedItem.exists) resultRawList.Add(incrementedItem.item, targetInt); + } + }); + + return new ListValue(resultRawList); + } }, { key: 'CoerceValuesToSingleType', value: function CoerceValuesToSingleType(parametersIn) { var valType = ValueType.Int; + var specialCaseList = null; + // Find out what the output type is // "higher level" types infect both so that binary operations // use the same type on both sides. e.g. binary operation of @@ -1786,14 +2244,40 @@ if (val.valueType > valType) { valType = val.valueType; } + + if (val.valueType == ValueType.List) { + // specialCaseList = val as ListValue; + specialCaseList = val; + } }); // Coerce to this chosen type var parametersOut = []; - parametersIn.forEach(function (val) { - var castedValue = val.Cast(valType); - parametersOut.push(castedValue); - }); + + if (valType == ValueType.List) { + parametersIn.forEach(function (val) { + if (val.valueType == ValueType.List) { + parametersOut.push(val); + } else if (val.valueType == ValueType.Int) { + var intVal = parseInt(val.valueObject); + var list = specialCaseList.value.originOfMaxItem; + + var item = list.TryGetItemWithValue(intVal); + if (item.exists) { + var castedValue = new ListValue(item.item, intVal); + parametersOut.push(castedValue); + } else throw new StoryException("Could not find List item with the value " + intVal + " in " + list.name); + } else throw new StoryException("Cannot mix Lists and " + val.valueType + " values in this operation"); + }); + } + + // Normal Coercing (with standard casting) + else { + parametersIn.forEach(function (val) { + var castedValue = val.Cast(valType); + parametersOut.push(castedValue); + }); + } return parametersOut; } @@ -1868,7 +2352,7 @@ return x * y; }); this.AddIntBinaryOp(this.Divide, function (x, y) { - return x / y; + return parseInt(x / y); }); this.AddIntBinaryOp(this.Mod, function (x, y) { return x % y; @@ -1977,6 +2461,67 @@ return x === y ? 1 : 0; }); + this.AddListBinaryOp(this.Add, function (x, y) { + return x.Union(y); + }); + this.AddListBinaryOp(this.And, function (x, y) { + return x.Union(y); + }); + this.AddListBinaryOp(this.Subtract, function (x, y) { + return x.Without(y); + }); + this.AddListBinaryOp(this.Has, function (x, y) { + return x.Contains(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.Hasnt, function (x, y) { + return x.Contains(y) ? 0 : 1; + }); + this.AddListBinaryOp(this.Intersect, function (x, y) { + return x.Intersect(y); + }); + + this.AddListBinaryOp(this.Equal, function (x, y) { + return x.Equals(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.Greater, function (x, y) { + return x.GreaterThan(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.Less, function (x, y) { + return x.LessThan(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.GreaterThanOrEquals, function (x, y) { + return x.GreaterThanOrEquals(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.LessThanOrEquals, function (x, y) { + return x.LessThanOrEquals(y) ? 1 : 0; + }); + this.AddListBinaryOp(this.NotEquals, function (x, y) { + return !x.Equals(y) ? 1 : 0; + }); + + this.AddListUnaryOp(this.Not, function (x) { + return x.Count == 0 ? 1 : 0; + }); + + this.AddListUnaryOp(this.Invert, function (x) { + return x.inverse; + }); + this.AddListUnaryOp(this.All, function (x) { + return x.all; + }); + this.AddListUnaryOp(this.ListMin, function (x) { + return x.MinAsList(); + }); + this.AddListUnaryOp(this.ListMax, function (x) { + return x.MaxAsList(); + }); + this.AddListUnaryOp(this.Count, function (x) { + return x.Count; + }); + this.AddListUnaryOp(this.ValueOfList, function (x) { + return x.maxItem.Value; + }); + // Special case: The only operation you can do on divert target values var divertTargetsEqual = function divertTargetsEqual(d1, d2) { return d1.Equals(d2) ? 1 : 0; @@ -2020,16 +2565,26 @@ value: function AddStringBinaryOp(name, op) { this.AddOpToNativeFunc(name, 2, ValueType.String, op); } + }, { + key: 'AddListBinaryOp', + value: function AddListBinaryOp(name, op) { + this.AddOpToNativeFunc(name, 2, ValueType.List, op); + } + }, { + key: 'AddListUnaryOp', + value: function AddListUnaryOp(name, op) { + this.AddOpToNativeFunc(name, 1, ValueType.List, op); + } }]); return NativeFunctionCall; - }(InkObject); + }(Object$1); NativeFunctionCall.Add = "+"; NativeFunctionCall.Subtract = "-"; NativeFunctionCall.Divide = "/"; NativeFunctionCall.Multiply = "*"; NativeFunctionCall.Mod = "%"; - NativeFunctionCall.Negate = "~"; + NativeFunctionCall.Negate = "_"; NativeFunctionCall.Equal = "=="; NativeFunctionCall.Greater = ">"; @@ -2045,28 +2600,44 @@ NativeFunctionCall.Min = "MIN"; NativeFunctionCall.Max = "MAX"; + NativeFunctionCall.Has = "?"; + NativeFunctionCall.Hasnt = "!?"; + NativeFunctionCall.Intersect = "^"; + + NativeFunctionCall.ListMin = "LIST_MIN"; + NativeFunctionCall.ListMax = "LIST_MAX"; + NativeFunctionCall.All = "LIST_ALL"; + NativeFunctionCall.Count = "LIST_COUNT"; + NativeFunctionCall.ValueOfList = "LIST_VALUE"; + NativeFunctionCall.Invert = "LIST_INVERT"; + NativeFunctionCall._nativeFunctions = null; - var Tag = function () { + var Tag = function (_InkObject) { + babelHelpers.inherits(Tag, _InkObject); + function Tag(tagText) { babelHelpers.classCallCheck(this, Tag); - this._text = tagText.toString() || ''; + var _this = babelHelpers.possibleConstructorReturn(this, (Tag.__proto__ || Object.getPrototypeOf(Tag)).call(this)); + + _this._text = tagText.toString() || ''; + return _this; } babelHelpers.createClass(Tag, [{ - key: "toString", + key: 'toString', value: function toString() { return "# " + this._text; } }, { - key: "text", + key: 'text', get: function get() { return this._text; } }]); return Tag; - }(); + }(Object$1); var Choice = function () { function Choice(choice) { @@ -2088,10 +2659,167 @@ get: function get() { return this.choicePoint.pathStringOnChoice; } + }, { + key: "sourcePath", + get: function get() { + return this.choicePoint.path.componentsString; + } }]); return Choice; }(); + var ListDefinition = function () { + function ListDefinition(name, items) { + babelHelpers.classCallCheck(this, ListDefinition); + + this._name = name || ''; + this._items = null; + this._rawListItemsKeys = null; + this._itemNameToValues = items || {}; + } + + babelHelpers.createClass(ListDefinition, [{ + key: 'forEachItems', + value: function forEachItems(fn) { + for (var key in this._rawListItemsKeys) { + fn({ + Key: this._rawListItemsKeys[key], + Value: this._items[key] + }); + } + } + }, { + key: 'ValueForItem', + value: function ValueForItem(item) { + var intVal = this._itemNameToValues[item.itemName]; + if (intVal !== undefined) return intVal;else return 0; + } + }, { + key: 'ContainsItem', + value: function ContainsItem(item) { + if (item.originName != this.name) return false; + + return item.itemName in this._itemNameToValues; + } + }, { + key: 'TryGetItemWithValue', + value: function TryGetItemWithValue(val, item) { + //item was an out + //the original function returns a boolean and has a second parameter called item that is an `out`. Both are needed and we can't just return the item because it'll always be truthy. Instead, we return an object containing the bool an dthe item + for (var key in this._itemNameToValues) { + if (this._itemNameToValues[key] == val) { + item = new RawListItem(this.name, key); + return { + item: item, + exists: true + }; + } + } + + item = RawListItem.Null; + return { + item: item, + exists: false + }; + } + }, { + key: 'ListRange', + value: function ListRange(min, max) { + var rawList = new RawList(); + for (var key in this._itemNameToValues) { + if (this._itemNameToValues[key] >= min && this._itemNameToValues[key] <= max) { + var item = new RawListItem(this.name, key); + rawList.Add(item, this._itemNameToValues[key]); + } + } + return new ListValue(rawList); + } + }, { + key: 'name', + get: function get() { + return this._name; + } + }, { + key: 'items', + get: function get() { + if (this._items == null) { + this._items = {}; + this._rawListItemsKeys = {}; + for (var key in this._itemNameToValues) { + var item = new RawListItem(this.name, key); + this._rawListItemsKeys[item] = item; + this._items[item] = this._itemNameToValues[key]; + } + } + this._items.forEach = this.forEachItems.bind(this); + + return this._items; + } + }]); + return ListDefinition; + }(); + + var ListDefinitionsOrigin = function () { + function ListDefinitionsOrigin(lists) { + var _this = this; + + babelHelpers.classCallCheck(this, ListDefinitionsOrigin); + + this._lists = {}; + + lists.forEach(function (list) { + _this._lists[list.name] = list; + }); + } + + babelHelpers.createClass(ListDefinitionsOrigin, [{ + key: 'TryGetDefinition', + value: function TryGetDefinition(name, def) { + //initially, this function returns a boolean and the second parameter is an out. + return name in this._lists ? this._lists[name] : def; + } + }, { + key: 'FindSingleItemListWithName', + value: function FindSingleItemListWithName(name) { + var item = RawListItem.Null; + var list = null; + + var nameParts = name.split('.'); + if (nameParts.length == 2) { + item = new RawListItem(nameParts[0], nameParts[1]); + list = this.TryGetDefinition(item.originName, list); + } else { + for (var key in this._lists) { + var listWithItem = this._lists[key]; + item = new RawListItem(key, name); + if (listWithItem.ContainsItem(item)) { + list = listWithItem; + break; + } + } + } + + if (list != null) { + var itemValue = list.ValueForItem(item); + return new ListValue(item, itemValue); + } + + return null; + } + }, { + key: 'lists', + get: function get() { + var listOfLists = []; + + for (var key in this._lists) { + listOfLists.push(this._lists[key]); + } + return listOfLists; + } + }]); + return ListDefinitionsOrigin; + }(); + var JsonSerialisation = function () { function JsonSerialisation() { babelHelpers.classCallCheck(this, JsonSerialisation); @@ -2143,7 +2871,7 @@ for (var key in dictionary) { // var runtimeObj = keyVal.Value as Runtime.Object; var runtimeObj = dictionary[key]; - if (runtimeObj instanceof InkObject) jsonObj[key] = this.RuntimeObjectToJToken(runtimeObj); + if (runtimeObj instanceof Object$1) jsonObj[key] = this.RuntimeObjectToJToken(runtimeObj); } return jsonObj; @@ -2194,6 +2922,7 @@ } // Native functions + if (str == "L^") str = "^"; if (NativeFunctionCall.CallExistsWithName(str)) return NativeFunctionCall.CallWithName(str); // Pop @@ -2305,6 +3034,28 @@ return new Tag(propValue.toString()); } + //list value + if (propValue = obj["list"]) { + // var listContent = (Dictionary)propValue; + var listContent = propValue; + var rawList = new RawList(); + if (propValue = obj["origins"]) { + // var namesAsObjs = (List)propValue; + var namesAsObjs = propValue; + // rawList.SetInitialOriginNames(namesAsObjs.Cast().ToList()); + rawList.SetInitialOriginNames(namesAsObjs); + } + + for (var key in listContent) { + var nameToVal = listContent[key]; + var item = new RawListItem(key); + var val = parseInt(nameToVal); + rawList.Add(item, val); + } + + return new ListValue(rawList); + } + if (obj["originalChoicePath"] != null) return this.JObjectToChoice(obj); } @@ -2372,6 +3123,12 @@ if (strVal.isNewline) return "\n";else return "^" + strVal.value; } + // var listVal = obj as ListValue; + var listVal = obj; + if (listVal instanceof ListValue) { + return this.InkListToJObject(listVal); + } + // var divTargetVal = obj as DivertTargetValue; var divTargetVal = obj; if (divTargetVal instanceof DivertTargetValue) return { @@ -2399,7 +3156,13 @@ // var nativeFunc = obj as Runtime.NativeFunctionCall; var nativeFunc = obj; - if (nativeFunc instanceof NativeFunctionCall) return nativeFunc.name; + if (nativeFunc instanceof NativeFunctionCall) { + var name = nativeFunc.name; + + // Avoid collision with ^ used to indicate a string + if (name == "^") name = "L^"; + return name; + } // Variable reference // var varRef = obj as VariableReference; @@ -2551,6 +3314,75 @@ jObj["originalThreadIndex"] = choice.originalThreadIndex; return jObj; } + }, { + key: 'InkListToJObject', + value: function InkListToJObject(listVal) { + var rawList = listVal.value; + + var dict = {}; + + var content = {}; + + rawList.forEach(function (itemAndValue) { + var item = itemAndValue.Key; + var val = itemAndValue.Value; + content[item.toString()] = val; + }); + + dict["list"] = content; + + if (rawList.Count == 0 && rawList.originNames != null && rawList.originNames.length > 0) { + // dict["origins"] = rawList.originNames.Cast ().ToList (); + dict["origins"] = rawList.originNames; + } + + return dict; + } + }, { + key: 'ListDefinitionsToJToken', + value: function ListDefinitionsToJToken(origin) { + var result = {}; + + origin.lists.forEach(function (def) { + var listDefJson = {}; + def.items.forEach(function (itemToVal) { + var item = itemToVal.Key; + var val = itemToVal.Value; + listDefJson[item.itemName] = val; + }); + + result[def.name] = listDefJson; + }); + + return result; + } + }, { + key: 'JTokenToListDefinitions', + value: function JTokenToListDefinitions(obj) { + // var defsObj = (Dictionary)obj; + var defsObj = obj; + + var allDefs = []; + + for (var key in defsObj) { + var name = key.toString(); + // var listDefJson = (Dictionary)kv.Value; + var listDefJson = defsObj[key]; + + // Cast (string, object) to (string, int) for items + var items = {}; + + for (var nameValueKey in listDefJson) { + var nameValue = listDefJson[nameValueKey]; + items[nameValueKey] = parseInt(nameValue); + } + + var def = new ListDefinition(name, items); + allDefs.push(def); + } + + return new ListDefinitionsOrigin(allDefs); + } }]); return JsonSerialisation; }(); @@ -2576,6 +3408,8 @@ _controlCommandNames[ControlCommand.CommandType.StartThread] = "thread"; _controlCommandNames[ControlCommand.CommandType.Done] = "done"; _controlCommandNames[ControlCommand.CommandType.End] = "end"; + _controlCommandNames[ControlCommand.CommandType.ListFromInt] = "listInt"; + _controlCommandNames[ControlCommand.CommandType.ListRange] = "range"; for (var i$1 = 0; i$1 < ControlCommand.CommandType.TOTAL_VALUES; ++i$1) { if (_controlCommandNames[i$1] == null) throw "Control command not accounted for in serialisation"; @@ -2784,8 +3618,8 @@ if (this.canPopThread) { this._threads.splice(this._threads.indexOf(this.currentThread), 1); //should be equivalent to a pop() } else { - throw "Can't pop thread"; - } + throw "Can't pop thread"; + } } }, { key: 'SetJsonToken', @@ -2850,6 +3684,9 @@ throw new StoryException("Could not find temporary variable to set: " + name); } + var oldValue; + if (oldValue = contextElement.temporaryVariables[name]) ListValue.RetainListOriginsForAssignment(oldValue, value); + contextElement.temporaryVariables[name] = value; } }, { @@ -2920,12 +3757,16 @@ return CallStack; }(); + //still needs: + // - varchanged events + // - see if the internal getenumarators are needed var VariablesState = function () { - function VariablesState(callStack) { + function VariablesState(callStack, listDefsOrigin) { babelHelpers.classCallCheck(this, VariablesState); this._globalVariables = {}; this._callStack = callStack; + this._listDefsOrigin = listDefsOrigin; this._batchObservingVariableChanges = null; this._changedVariables = null; @@ -2978,15 +3819,16 @@ } }, { key: 'CopyFrom', - value: function CopyFrom(varState) { - this._globalVariables = varState._globalVariables; - this.variableChangedEvent = varState.variableChangedEvent; + value: function CopyFrom(toCopy) { + this._globalVariables = babelHelpers.extends({}, toCopy._globalVariables); + + this.variableChangedEvent = toCopy.variableChangedEvent; - if (varState.batchObservingVariableChanges != this.batchObservingVariableChanges) { + if (toCopy.batchObservingVariableChanges != this.batchObservingVariableChanges) { - if (varState.batchObservingVariableChanges) { + if (toCopy.batchObservingVariableChanges) { this._batchObservingVariableChanges = true; - this._changedVariables = varState._changedVariables; + this._changedVariables = toCopy._changedVariables; } else { this._batchObservingVariableChanges = false; this._changedVariables = null; @@ -3017,6 +3859,9 @@ // 0 context = global if (contextIndex == 0 || contextIndex == -1) { if (varValue = this._globalVariables[name]) return varValue; + + var listItemValue = this._listDefsOrigin.FindSingleItemListWithName(name); + if (listItemValue) return listItemValue; } // Temporary @@ -3078,12 +3923,24 @@ this._callStack.SetTemporaryVariable(name, value, varAss.isNewDeclaration, contextIndex); } } + }, { + key: 'RetainListOriginsForAssignment', + value: function RetainListOriginsForAssignment(oldValue, newValue) { + // var oldList = oldValue as ListValue; + var oldList = oldValue; + // var newList = newValue as ListValue; + var newList = newValue; + + if (oldList instanceof ListValue && newList instanceof ListValue && newList.value.Count == 0) newList.value.SetInitialOriginNames(oldList.value.originNames); + } }, { key: 'SetGlobal', value: function SetGlobal(variableName, value) { var oldValue = null; oldValue = this._globalVariables[variableName]; + ListValue.RetainListOriginsForAssignment(oldValue, value); + this._globalVariables[variableName] = value; if (this.variableChangedEvent != null && value !== oldValue) { @@ -3105,7 +3962,7 @@ var valueOfVariablePointedTo = this.GetRawVariableWithName(varPointer.variableName, contextIndex); // Extra layer of indirection: - // When accessing a pointer to a pointer (e.g. when calling nested or + // When accessing a pointer to a pointer (e.g. when calling nested or // recursive functions that take a variable references, ensure we don't create // a chain of indirection by just returning the final target. // var doubleRedirectionPointer = valueOfVariablePointedTo as VariablePointerValue; @@ -3168,7 +4025,7 @@ this._changedVariables = []; } - // Finished observing variables in a batch - now send + // Finished observing variables in a batch - now send // notifications for changed variables all in one go. else { if (this._changedVariables != null) { @@ -3225,11 +4082,14 @@ this.story = story; this._outputStream = []; + this._outputStreamTextDirty = true; + this._outputStreamTagsDirty = true; + this.OutputStreamDirty(); this._evaluationStack = []; this.callStack = new CallStack(story.rootContentContainer); - this._variablesState = new VariablesState(this.callStack); + this._variablesState = new VariablesState(this.callStack, story.listDefinitions); this._visitCounts = {}; this._turnIndices = {}; @@ -3242,14 +4102,37 @@ this.previousRandom = 0; this._currentChoices = []; + this._currentText = null; + this._currentTags = null; this._currentErrors = null; this.didSafeExit = false; + this._isExternalFunctionEvaluation = false; + this._originalCallstack = null; + this._originalEvaluationStackHeight = 0; + this.GoToStart(); } babelHelpers.createClass(StoryState, [{ + key: 'MatchRightGlueForLeftGlue', + value: function MatchRightGlueForLeftGlue(leftGlue) { + if (!leftGlue.isLeft) return null; + + for (var i = this._outputStream.length - 1; i >= 0; i--) { + var c = this._outputStream[i]; + // var g = c as Glue; + var g = c; + if (g instanceof Glue && g.isRight && g.parent == leftGlue.parent) { + return g; + } else if (c instanceof ControlCommand) // e.g. BeginString + break; + } + + return null; + } + }, { key: 'GoToStart', value: function GoToStart() { this.callStack.currentElement.currentContainer = this.story.mainContentContainer; @@ -3264,10 +4147,33 @@ key: 'ResetOutput', value: function ResetOutput() { this._outputStream.length = 0; + this.OutputStreamDirty(); } }, { key: 'PushEvaluationStack', value: function PushEvaluationStack(obj) { + var _this = this; + + // var listValue = obj as ListValue; + var listValue = obj; + if (listValue instanceof ListValue) { + + // Update origin when list is has something to indicate the list origin + var rawList = listValue.value; + var names = rawList.originNames; + if (names != null) { + var origins = []; + + names.forEach(function (n) { + var def = null; + def = _this.story.listDefinitions.TryGetDefinition(n, def); + if (origins.indexOf(def) < 0) origins.push(def); + }); + + rawList.origins = origins; + } + } + this.evaluationStack.push(obj); } }, { @@ -3293,7 +4199,7 @@ }, { key: 'PushToOutputStream', value: function PushToOutputStream(obj) { - var _this = this; + var _this2 = this; // var text = obj as StringValue; var text = obj; @@ -3301,13 +4207,14 @@ var listText = this.TrySplittingHeadTailWhitespace(text); if (listText != null) { listText.forEach(function (textObj) { - _this.PushToOutputStreamIndividual(textObj); + _this2.PushToOutputStreamIndividual(textObj); }); return; } } this.PushToOutputStreamIndividual(obj); + this.OutputStreamDirty(); } }, { key: 'TrySplittingHeadTailWhitespace', @@ -3382,12 +4289,15 @@ // Found matching left-glue for right-glue? Close it. var existingRightGlue = this.currentRightGlue; var foundMatchingLeftGlue = !!(glue.isLeft && existingRightGlue && glue.parent == existingRightGlue.parent); + var matchingRightGlue = null; - // Left/Right glue is auto-generated for inline expressions + if (glue.isLeft) matchingRightGlue = this.MatchRightGlueForLeftGlue(glue); + + // Left/Right glue is auto-generated for inline expressions // where we want to absorb newlines but only in a certain direction. // "Bi" glue is written by the user in their ink with <> if (glue.isLeft || glue.isBi) { - this.TrimNewlinesFromOutputStream(foundMatchingLeftGlue); + this.TrimNewlinesFromOutputStream(matchingRightGlue); } includeInOutput = glue.isBi || glue.isRight; @@ -3403,7 +4313,7 @@ includeInOutput = false; } - // Able to completely reset when + // Able to completely reset when else if (text.isNonWhitespace) { this.RemoveExistingGlue(); } @@ -3414,11 +4324,12 @@ if (includeInOutput) { this._outputStream.push(obj); + this.OutputStreamDirty(); } } }, { key: 'TrimNewlinesFromOutputStream', - value: function TrimNewlinesFromOutputStream(stopAndRemoveRightGlue) { + value: function TrimNewlinesFromOutputStream(rightGlueToStopAt) { var removeWhitespaceFrom = -1; var rightGluePos = -1; var foundNonWhitespace = false; @@ -3439,8 +4350,8 @@ if (cmd instanceof ControlCommand || txt instanceof StringValue && txt.isNonWhitespace) { foundNonWhitespace = true; - if (!stopAndRemoveRightGlue) break; - } else if (stopAndRemoveRightGlue && glue instanceof Glue && glue.isRight) { + if (rightGlueToStopAt == null) break; + } else if (rightGlueToStopAt && glue instanceof Glue && glue == rightGlueToStopAt) { rightGluePos = i; break; } else if (txt instanceof StringValue && txt.isNewline && !foundNonWhitespace) { @@ -3463,9 +4374,18 @@ } } - // Remove the glue (it will come before the whitespace, - // so index is still valid) - if (stopAndRemoveRightGlue && rightGluePos > -1) this._outputStream.splice(rightGluePos, 1); + if (rightGlueToStopAt && rightGluePos > -1) { + i = rightGluePos; + while (i < this._outputStream.length) { + if (this._outputStream[i] instanceof Glue && this._outputStream[i].isRight) { + this.outputStream.splice(i, 1); + } else { + i++; + } + } + } + + this.OutputStreamDirty(); } }, { key: 'TrimFromExistingGlue', @@ -3476,6 +4396,8 @@ var txt = this._outputStream[i]; if (txt instanceof StringValue && !txt.isNonWhitespace) this._outputStream.splice(i, 1);else i++; } + + this.OutputStreamDirty(); } }, { key: 'RemoveExistingGlue', @@ -3489,17 +4411,20 @@ break; } } + + this.OutputStreamDirty(); } }, { key: 'ForceEnd', value: function ForceEnd() { - this.currentContentObject = null; - while (this.callStack.canPopThread) { this.callStack.PopThread(); }while (this.callStack.canPop) { this.callStack.Pop(); - }this.currentChoices.length = 0; + }this._currentChoices.length = 0; + + this.currentContentObject = null; + this.previousContentObject = null; this.didSafeExit = true; } @@ -3507,12 +4432,90 @@ key: 'SetChosenPath', value: function SetChosenPath(path) { // Changing direction, assume we need to clear current set of choices - this.currentChoices.length = 0; + this._currentChoices.length = 0; this.currentPath = path; this._currentTurnIndex++; } + }, { + key: 'StartExternalFunctionEvaluation', + value: function StartExternalFunctionEvaluation(funcContainer, args) { + // We'll start a new callstack, so keep hold of the original, + // as well as the evaluation stack so we know if the function + // returned something + this._originalCallstack = this.callStack; + this._originalEvaluationStackHeight = this.evaluationStack.length; + + // Create a new base call stack element. + this.callStack = new CallStack(funcContainer); + this.callStack.currentElement.type = PushPopType.Function; + + // By setting ourselves in external function evaluation mode, + // we're saying it's okay to end the flow without a Done or End, + // but with a ~ return instead. + this._isExternalFunctionEvaluation = true; + + // Pass arguments onto the evaluation stack + if (args != null) { + 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.PushEvaluationStack(Value.Create(args[i])); + } + } + } + }, { + key: 'TryExitExternalFunctionEvaluation', + value: function TryExitExternalFunctionEvaluation() { + if (this._isExternalFunctionEvaluation && this.callStack.elements.length == 1 && this.callStack.currentElement.type == PushPopType.Function) { + this.currentContentObject = null; + this.didSafeExit = true; + return true; + } + + return false; + } + }, { + key: 'CompleteExternalFunctionEvaluation', + value: function CompleteExternalFunctionEvaluation() { + // Do we have a returned value? + // Potentially pop multiple values off the stack, in case we need + // to clean up after ourselves (e.g. caller of EvaluateFunction may + // have passed too many arguments, and we currently have no way to check for that) + var returnedObj = null; + while (this.evaluationStack.length > this._originalEvaluationStackHeight) { + var poppedObj = this.PopEvaluationStack(); + if (returnedObj == null) returnedObj = poppedObj; + } + + // Restore our own state + this.callStack = this._originalCallstack; + this._originalCallstack = null; + this._originalEvaluationStackHeight = 0; + + if (returnedObj) { + if (returnedObj instanceof Void) return null; + + // Some kind of value, if not void + // var returnVal = returnedObj as Runtime.Value; + var returnVal = returnedObj; + + // 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(); + } + + // Other types can just have their exact object type: + // int, float, string. VariablePointers get returned as strings. + return returnVal.valueObject; + } + + return null; + } }, { key: 'AddError', value: function AddError(message) { @@ -3522,6 +4525,12 @@ this._currentErrors.push(message); } + }, { + key: 'OutputStreamDirty', + value: function OutputStreamDirty() { + this._outputStreamTextDirty = true; + this._outputStreamTagsDirty = true; + } }, { key: 'VisitCountAtPathString', value: function VisitCountAtPathString(pathString) { @@ -3536,7 +4545,9 @@ var copy = new StoryState(this.story); copy.outputStream.push.apply(copy.outputStream, this._outputStream); - copy.currentChoices.push.apply(copy.currentChoices, this.currentChoices); + this.OutputStreamDirty(); + + copy._currentChoices.push.apply(copy._currentChoices, this._currentChoices); if (this.hasError) { copy.currentErrors = []; @@ -3545,7 +4556,7 @@ copy.callStack = new CallStack(this.callStack); - copy._variablesState = new VariablesState(copy.callStack); + copy._variablesState = new VariablesState(copy.callStack, this.story.listDefinitions); copy.variablesState.CopyFrom(this.variablesState); copy.evaluationStack.push.apply(copy.evaluationStack, this.evaluationStack); @@ -3554,8 +4565,17 @@ copy.previousContentObject = this.previousContentObject; - copy._visitCounts = this._visitCounts; - copy._turnIndices = this._turnIndices; + copy._isExternalFunctionEvaluation = this._isExternalFunctionEvaluation; + + copy._visitCounts = {}; + for (var keyValue in this._visitCounts) { + copy._visitCounts[keyValue] = this._visitCounts[keyValue]; + } + copy._turnIndices = {}; + for (var keyValue in this._turnIndices) { + copy._turnIndices[keyValue] = this._turnIndices[keyValue]; + } + copy._currentTurnIndex = this.currentTurnIndex; copy.storySeed = this.storySeed; copy.previousRandom = this.previousRandom; @@ -3576,6 +4596,15 @@ } }, { key: 'currentChoices', + get: function get() { + // If we can continue generating text content rather than choices, + // then we reflect the choice list as being empty, since choices + // should always come at the end. + if (this.canContinue) return []; + return this._currentChoices; + } + }, { + key: 'generatedChoices', get: function get() { return this._currentChoices; } @@ -3612,6 +4641,11 @@ set: function set(value) { this.callStack.currentElement.currentObject = value; } + }, { + key: 'canContinue', + get: function get() { + return this.currentContentObject != null && !this.hasError; + } }, { key: 'hasError', get: function get() { @@ -3696,32 +4730,43 @@ }, { key: 'currentText', get: function get() { - var sb = new StringBuilder(); + if (this._outputStreamTextDirty) { + var sb = new StringBuilder(); - this._outputStream.forEach(function (outputObj) { - // var textContent = outputObj as StringValue; - var textContent = outputObj; - if (textContent instanceof StringValue) { - sb.Append(textContent.value); - } - }); + this._outputStream.forEach(function (outputObj) { + // var textContent = outputObj as StringValue; + var textContent = outputObj; + if (textContent instanceof StringValue) { + sb.Append(textContent.value); + } + }); - return sb.toString(); + this._currentText = sb.toString(); + this._outputStreamTextDirty = false; + } + + return this._currentText; } }, { key: 'currentTags', get: function get() { - var tags = []; + var _this3 = this; - this._outputStream.forEach(function (outputObj) { - // var tag = outputObj as Tag; - var tag = outputObj; - if (tag instanceof Tag) { - tags.push(tag.text); - } - }); + if (this._outputStreamTagsDirty) { + this._currentTags = []; - return tags; + this._outputStream.forEach(function (outputObj) { + // var tag = outputObj as Tag; + var tag = outputObj; + if (tag instanceof Tag) { + _this3._currentTags.push(tag.text); + } + }); + + this._outputStreamTagsDirty = false; + } + + return this._currentTags; } }, { key: 'outputStream', @@ -3754,16 +4799,16 @@ }, { key: 'jsonToken', get: function get() { - var _this2 = this; + var _this4 = this; var obj = {}; var choiceThreads = null; - this.currentChoices.forEach(function (c) { + this._currentChoices.forEach(function (c) { c.originalChoicePath = c.choicePoint.path.componentsString; c.originalThreadIndex = c.threadAtGeneration.threadIndex; - if (_this2.callStack.ThreadWithIndex(c.originalThreadIndex) == null) { + if (_this4.callStack.ThreadWithIndex(c.originalThreadIndex) == null) { if (choiceThreads == null) choiceThreads = {}; choiceThreads[c.originalThreadIndex.toString()] = c.threadAtGeneration.jsonToken; @@ -3779,7 +4824,7 @@ obj["outputStream"] = JsonSerialisation.ListToJArray(this._outputStream); - obj["currentChoices"] = JsonSerialisation.ListToJArray(this.currentChoices); + obj["currentChoices"] = JsonSerialisation.ListToJArray(this._currentChoices); if (this.divertedTargetObject != null) obj["currentDivertTarget"] = this.divertedTargetObject.path.componentsString; @@ -3796,7 +4841,7 @@ return obj; }, set: function set(value) { - var _this3 = this; + var _this5 = this; var jObject = value; @@ -3813,6 +4858,7 @@ this._evaluationStack = JsonSerialisation.JArrayToRuntimeObjList(jObject["evalStack"]); this._outputStream = JsonSerialisation.JArrayToRuntimeObjList(jObject["outputStream"]); + this.OutputStreamDirty(); // currentChoices = Json.JArrayToRuntimeObjList((JArray)jObject ["currentChoices"]); this._currentChoices = JsonSerialisation.JArrayToRuntimeObjList(jObject["currentChoices"]); @@ -3831,15 +4877,15 @@ // var jChoiceThreads = jObject["choiceThreads"] as JObject; var jChoiceThreads = jObject["choiceThreads"]; - this.currentChoices.forEach(function (c) { - c.choicePoint = _this3.story.ContentAtPath(new Path$1(c.originalChoicePath)); + this._currentChoices.forEach(function (c) { + c.choicePoint = _this5.story.ContentAtPath(new Path$1(c.originalChoicePath)); - var foundActiveThread = _this3.callStack.ThreadWithIndex(c.originalThreadIndex); + var foundActiveThread = _this5.callStack.ThreadWithIndex(c.originalThreadIndex); if (foundActiveThread != null) { c.threadAtGeneration = foundActiveThread; } else { var jSavedChoiceThread = jChoiceThreads[c.originalThreadIndex.toString()]; - c.threadAtGeneration = new CallStack.Thread(jSavedChoiceThread, _this3.story); + c.threadAtGeneration = new CallStack.Thread(jSavedChoiceThread, _this5.story); } }); } @@ -3847,8 +4893,8 @@ return StoryState; }(); - StoryState.kInkSaveStateVersion = 5; - StoryState.kMinCompatibleLoadVersion = 4; + StoryState.kInkSaveStateVersion = 6; + StoryState.kMinCompatibleLoadVersion = 6; if (!Number.isInteger) { Number.isInteger = function isInteger(nVal) { @@ -3859,21 +4905,27 @@ var Story = function (_InkObject) { babelHelpers.inherits(Story, _InkObject); - function Story(jsonString) { + function Story(jsonString, lists) { babelHelpers.classCallCheck(this, Story); - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(Story).call(this)); + var _this = babelHelpers.possibleConstructorReturn(this, (Story.__proto__ || Object.getPrototypeOf(Story)).call(this)); + + lists = lists || null; - _this.inkVersionCurrent = 14; - _this.inkVersionMinimumCompatible = 12; + _this.inkVersionCurrent = 16; + _this.inkVersionMinimumCompatible = 16; _this._variableObservers = null; _this._externals = {}; + _this._prevContainerSet = null; + _this._listDefinitions = null; if (jsonString instanceof Container) { _this._mainContentContainer = jsonString; + + if (lists != null) _this._listDefinitions = new ListDefinitionsOrigin(lists); } else { - //the original version only accepts a string as a constructor, but this is javascript and it's almost easier to get a JSON value than a string, so we're silently accepting btoh + //the original version only accepts a string as a constructor, but this is javascript and it's almost easier to get a JSON value than a string, so we're silently accepting both var rootObject = typeof jsonString === 'string' ? JSON.parse(jsonString) : jsonString; var versionObj = rootObject["inkVersion"]; @@ -3891,6 +4943,11 @@ var rootToken = rootObject["root"]; if (rootToken == null) throw "Root node for ink not found. Are you sure it's a valid .ink.json file?"; + var listDefsObj; + if (listDefsObj = rootObject["listDefs"]) { + _this._listDefinitions = JsonSerialisation.JTokenToListDefinitions(listDefsObj); + } + _this._mainContentContainer = JsonSerialisation.JTokenToRuntimeObject(rootToken); _this._hasValidatedExternals = null; @@ -3910,6 +4967,8 @@ rootObject["inkVersion"] = this.inkVersionCurrent; rootObject["root"] = rootContainerJsonList; + if (this._listDefinitions != null) rootObject["listDefs"] = JsonSerialisation.ListDefinitionsToJToken(this._listDefinitions); + return JSON.stringify(rootObject); } }, { @@ -3979,7 +5038,7 @@ // which are actually built out of text content. // So we have to take a snapshot of the state, continue prospectively, // and rewind if necessary. - // This code is slightly fragile :-/ + // This code is slightly fragile :-/ // do { @@ -4030,7 +5089,13 @@ // We're going to continue stepping in case we see glue or some // non-text content such as choices. if (this.canContinue) { - stateAtLastNewline = this.StateSnapshot(); + // Don't bother to record the state beyond the current newline. + // e.g.: + // Hello world\n // record state at the end of here + // ~ complexCalculation() // don't actually need this unless it generates text + if (stateAtLastNewline == null) { + stateAtLastNewline = this.StateSnapshot(); + } } // Can't continue, so we're about to exit - make sure we @@ -4054,7 +5119,7 @@ this.Error("Thread available to pop, threads should always be flat by the end of evaluation?"); } - if (this.currentChoices.length == 0 && !this.state.didSafeExit && this._temporaryEvaluationContainer == null) { + if (this.state.generatedChoices.length == 0 && !this.state.didSafeExit && this._temporaryEvaluationContainer == null) { if (this.state.callStack.CanPop(PushPopType.Tunnel)) { this.Error("unexpectedly reached end of content. Do you need a '->->' to return from a tunnel?"); } else if (this.state.callStack.CanPop(PushPopType.Function)) { @@ -4155,7 +5220,7 @@ if (choicePoint instanceof ChoicePoint) { var choice = this.ProcessChoice(choicePoint); if (choice) { - this.state.currentChoices.push(choice); + this.state.generatedChoices.push(choice); } currentContentObj = null; @@ -4222,12 +5287,12 @@ if (!newContentObject) return; // First, find the previously open set of containers - var prevContainerSet = []; + if (this._prevContainerSet == null) this._prevContainerSet = []; if (previousContentObject) { // Container prevAncestor = previousContentObject as Container ?? previousContentObject.parent as Container; var prevAncestor = previousContentObject instanceof Container ? previousContentObject : previousContentObject.parent; while (prevAncestor instanceof Container) { - prevContainerSet.push(prevAncestor); + this._prevContainerSet.push(prevAncestor); // prevAncestor = prevAncestor.parent as Container; prevAncestor = prevAncestor.parent; } @@ -4238,7 +5303,7 @@ var currentChildOfContainer = newContentObject; // Container currentContainerAncestor = currentChildOfContainer.parent as Container; var currentContainerAncestor = currentChildOfContainer.parent; - while (currentContainerAncestor instanceof Container && prevContainerSet.indexOf(currentContainerAncestor) < 0) { + while (currentContainerAncestor instanceof Container && this._prevContainerSet.indexOf(currentContainerAncestor) < 0) { // Check whether this ancestor container is being entered at the start, // by checking whether the child object is the first. @@ -4435,22 +5500,38 @@ var popType = evalCommand.commandType == ControlCommand.CommandType.PopFunction ? PushPopType.Function : PushPopType.Tunnel; - if (this.state.callStack.currentElement.type != popType || !this.state.callStack.canPop) { + var overrideTunnelReturnTarget = null; + if (popType == PushPopType.Tunnel) { + var popped = this.state.PopEvaluationStack(); + // overrideTunnelReturnTarget = popped as DivertTargetValue; + overrideTunnelReturnTarget = popped; + if (overrideTunnelReturnTarget instanceof DivertTargetValue === false) { + if (popped instanceof Void === false) { + throw "Expected void if ->-> doesn't override target"; + } else { + overrideTunnelReturnTarget = null; + } + } + } + + if (this.state.TryExitExternalFunctionEvaluation()) { + break; + } else if (this.state.callStack.currentElement.type != popType || !this.state.callStack.canPop) { - var names = new {}(); + var names = {}; names[PushPopType.Function] = "function return statement (~ return)"; names[PushPopType.Tunnel] = "tunnel onwards statement (->->)"; var expected = names[this.state.callStack.currentElement.type]; - if (!this.state.callStack.canPop) { - expected = "end of flow (-> END or choice)"; - } + if (!this.state.callStack.canPop) expected = "end of flow (-> END or choice)"; var errorMsg = "Found " + names[popType] + ", when expected " + expected; this.Error(errorMsg); } else { this.state.callStack.Pop(); + + if (overrideTunnelReturnTarget) this.state.divertedTargetObject = this.ContentAtPath(overrideTunnelReturnTarget.targetPath); } break; @@ -4498,7 +5579,7 @@ break; case ControlCommand.CommandType.ChoiceCount: - var choiceCount = this.currentChoices.length; + var choiceCount = this.state.generatedChoices.length; this.state.PushEvaluationStack(new IntValue(choiceCount)); break; @@ -4592,6 +5673,79 @@ this.state.ForceEnd(); break; + case ControlCommand.CommandType.ListFromInt: + // var intVal = state.PopEvaluationStack () as IntValue; + var intVal = parseInt(this.state.PopEvaluationStack()); + // var listNameVal = state.PopEvaluationStack () as StringValue; + var listNameVal = this.state.PopEvaluationStack().toString(); + + var generatedListValue = null; + + var foundListDef; + if (foundListDef = this.listDefinitions.TryGetDefinition(listNameVal.value, foundListDef)) { + var foundItem = foundListDef.TryGetItemWithValue(intVal.value); + if (foundItem.exists) { + generatedListValue = new ListValue(foundItem.item, intVal.value); + } + } else { + throw new StoryException("Failed to find LIST called " + listNameVal.value); + } + + if (generatedListValue == null) generatedListValue = new ListValue(); + + this.state.PushEvaluationStack(generatedListValue); + break; + + case ControlCommand.CommandType.ListRange: + var max = this.state.PopEvaluationStack(); + var min = this.state.PopEvaluationStack(); + + // var targetList = state.PopEvaluationStack () as ListValue; + var targetList = this.state.PopEvaluationStack(); + + if (targetList instanceof ListValue === false || targetList == null || min == null || max == null) throw new StoryException("Expected list, minimum and maximum for LIST_RANGE"); + + // Allow either int or a particular list item to be passed for the bounds, + // so wrap up a function to handle this casting for us. + + var IntBound = function IntBound(obj) { + // var listValue = obj as ListValue; + var listValue = obj; + if (listValue instanceof ListValue) { + return parseInt(listValue.value.maxItem.Value); + } + + // var intValue = obj as IntValue; + var intValue = obj; + if (intValue instanceof IntValue) { + return intValue.value; + } + + return -1; + }; + + var minVal = IntBound(min); + var maxVal = IntBound(max); + if (minVal == -1) throw new StoryException("Invalid min range bound passed to LIST_VALUE(): " + min); + + if (maxVal == -1) throw new StoryException("Invalid max range bound passed to LIST_VALUE(): " + max); + + // Extract the range of items from the origin list + var result = new ListValue(); + var origins = targetList.value.origins; + + if (origins != null) { + origins.forEach(function (origin) { + var rangeFromOrigin = origin.ListRange(minVal, maxVal); + rangeFromOrigin.value.forEach(function (kv) { + result.value.Add(kv.Key, kv.Value); + }); + }); + } + + this.state.PushEvaluationStack(result); + break; + default: this.Error("unhandled ControlCommand: " + evalCommand); break; @@ -4638,7 +5792,7 @@ } } - this.state.evaluationStack.push(foundValue); + this.state.PushEvaluationStack(foundValue); return true; } @@ -4648,7 +5802,7 @@ var func = contentObj; var funcParams = this.state.PopEvaluationStack(func.numberOfParameters); var result = func.Call(funcParams); - this.state.evaluationStack.push(result); + this.state.PushEvaluationStack(result); return true; } @@ -4675,7 +5829,7 @@ var choices = this.currentChoices; if (choiceIdx < 0 || choiceIdx > choices.length) console.warn("choice out of range"); - // Replace callstack with the one from the thread at the choosing point, + // Replace callstack with the one from the thread at the choosing point, // so that we can jump into the right place in the flow. // This is important in case the flow was forked by a new thread, which // can create multiple leading edges for the story, each of @@ -4713,34 +5867,7 @@ if (e.message.indexOf("not found") >= 0) throw "Function doesn't exist: '" + functionName + "'";else throw e; } - // We'll start a new callstack, so keep hold of the original, - // as well as the evaluation stack so we know if the function - // returned something - var originalCallstack = this.state.callStack; - var originalEvaluationStackHeight = this.state.evaluationStack.length; - - // Create a new base call stack element. - // By making it point at element 0 of the base, when NextContent is - // called, it'll actually step past the entire content of the game (!) - // and straight onto the Done. Bit of a hack :-/ We don't really have - // a better way of creating a temporary context that ends correctly. - this.state.callStack = new CallStack(this.mainContentContainer); - this.state.callStack.currentElement.currentContainer = this.mainContentContainer; - this.state.callStack.currentElement.currentContentIndex = 0; - - if (args != null) { - 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.push(Value.Create(args[i])); - } - } - - // Jump into the function! - this.state.callStack.Push(PushPopType.Function); - this.state.currentContentObject = funcContainer; + this.state.StartExternalFunctionEvaluation(funcContainer, args); // Evaluate the function, and collect the string output var stringOutput = new StringBuilder(); @@ -4749,48 +5876,16 @@ } var textOutput = stringOutput.toString(); - // Restore original stack - this.state.callStack = originalCallstack; - - // Do we have a returned value? - // Potentially pop multiple values off the stack, in case we need - // to clean up after ourselves (e.g. caller of EvaluateFunction may - // have passed too many arguments, and we currently have no way to check for that) - var returnedObj = null; - while (this.state.evaluationStack.length > originalEvaluationStackHeight) { - var poppedObj = this.state.PopEvaluationStack(); - 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) returnedValue = null; - - // Some kind of value, if not void - // var returnVal = returnedObj as Runtime.Value; - var returnVal = returnedObj; - - // DivertTargets get returned as the string of components - // (rather than a Path, which isn't public) - if (returnVal.valueType == ValueType.DivertTarget) { - returnedValue = returnVal.valueObject.toString(); - } - - // Other types can just have their exact object type: - // int, float, string. VariablePointers get returned as strings. - returnedValue = returnVal.valueObject; - } + var result = this.state.CompleteExternalFunctionEvaluation(); - return returnTextOutput ? { 'returned': returnedValue, 'output': textOutput } : returnedValue; + return returnTextOutput ? { 'returned': result, 'output': textOutput } : result; } }, { key: 'EvaluateExpression', value: function EvaluateExpression(exprContainer) { var startCallStackHeight = this.state.callStack.elements.length; - this.state.callStack.push(PushPopType.Tunnel); + this.state.callStack.Push(PushPopType.Tunnel); this._temporaryEvaluationContainer = exprContainer; @@ -4832,7 +5927,7 @@ if (!(fallbackFunctionContainer instanceof Container)) console.warn("Trying to call EXTERNAL function '" + funcName + "' which has not been bound, and fallback ink function could not be found."); // Divert direct into fallback function and we're done - this.state.callStack.push(PushPopType.Function); + this.state.callStack.Push(PushPopType.Function); this.state.divertedTargetObject = fallbackFunctionContainer; return; } else { @@ -4904,27 +5999,45 @@ } }, { key: 'ValidateExternalBindings', - value: function ValidateExternalBindings(containerOrObject) { + value: function ValidateExternalBindings(containerOrObject, missingExternals) { var _this3 = this; if (!containerOrObject) { - this.ValidateExternalBindings(this._mainContentContainer); + var missingExternals = []; + this.ValidateExternalBindings(this._mainContentContainer, missingExternals); this._hasValidatedExternals = true; + + // No problem! Validation complete + if (missingExternals.length == 0) { + this._hasValidatedExternals = true; + } + + // Error for all missing externals + else { + var message = "Error: Missing function binding for external"; + message += missingExternals.length > 1 ? "s" : ""; + message += ": '"; + message += missingExternals.join("', '"); + message += "' "; + message += this.allowExternalFunctionFallbacks ? ", and no fallback ink function found." : " (ink fallbacks disabled)"; + + this.Error(message); + } } else if (containerOrObject instanceof Container) { var c = containerOrObject; c.content.forEach(function (innerContent) { - _this3.ValidateExternalBindings(innerContent); + _this3.ValidateExternalBindings(innerContent, missingExternals); }); for (var key in c.namedContent) { - this.ValidateExternalBindings(c.namedContent[key]); + this.ValidateExternalBindings(c.namedContent[key], missingExternals); } } else { var o = containerOrObject; //the following code is already taken care of above in this implementation // var container = o as Container; // if (container) { - // ValidateExternalBindings (container); + // ValidateExternalBindings (container, missingExternals); // return; // } @@ -4934,20 +6047,13 @@ var name = divert.targetPathString; if (!this._externals[name]) { - - var fallbackFunction = this.mainContentContainer.namedContent[name]; - var fallbackFound = typeof fallbackFunction !== 'undefined'; - - var message = null; - if (!this.allowExternalFunctionFallbacks) message = "Missing function binding for external '" + name + "' (ink fallbacks disabled)";else if (!fallbackFound) { - message = "Missing function binding for external '" + name + "', and no fallback ink function found."; - } - - if (message != null) { - var errorPreamble = "ERROR: "; - //misses a bit about metadata, which isn't implemented - - throw new StoryException(errorPreamble + message); + if (this.allowExternalFunctionFallbacks) { + var fallbackFound = !!this.mainContentContainer.namedContent[name]; + if (!fallbackFound) { + missingExternals.push(name); + } + } else { + missingExternals.push(name); } } } @@ -5022,15 +6128,15 @@ // 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]; + while (true) { + var firstContent = flowContainer.content[0]; + if (firstContent instanceof Container) flowContainer = firstContent;else break; + } // Any initial tag objects count as the "main tags" associated with that story/knot/stitch var tags = null; - innerWeaveContainer.content.every(function (c) { + flowContainer.content.every(function (c) { // var tag = c as Runtime.Tag; var tag = c; if (tag instanceof Tag) { @@ -5101,6 +6207,8 @@ this.state.callStack.PopThread(); didPop = true; + } else { + this.state.TryExitExternalFunctionEvaluation(); } // Step past the point where we last called out @@ -5318,6 +6426,11 @@ get: function get() { return this.state.variablesState; } + }, { + key: 'listDefinitions', + get: function get() { + return this._listDefinitions; + } }, { key: 'state', get: function get() { @@ -5335,7 +6448,7 @@ }, { key: 'canContinue', get: function get() { - return this.state.currentContentObject != null && !this.state.hasError; + return this.state.canContinue; } }, { key: 'globalTags', @@ -5344,8 +6457,10 @@ } }]); return Story; - }(InkObject); + }(Object$1); exports.Story = Story; -})); \ No newline at end of file + Object.defineProperty(exports, '__esModule', { value: true }); + +})));