diff --git a/README.md b/README.md index 17ef1e72..a2be7137 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,19 @@ console.log(inkStory.ContinueMaximally()); From there on, you can follow [the official documentation](https://github.com/inkle/ink/blob/master/Documentation/RunningYourInk.md#getting-started-with-the-runtime-api). +#### aggressive float parsing +Due to the nature of number in javascript, when loading a json file created from Inky (or inklecate), if floating point numbers that look like integer exists in you ink story (eg: something like `{5.0}`), they won't be understood correctly and that can lead to computation and rounding errors. (eg: `{7/3.0}` will display `2` instead of `2.33`). + +To correct this, you can load your story with `aggressiveFloatParsing` turned on : + +```javascript +var inkStory = new Story(json, {aggressiveFloatParsing: true}); + +console.log(inkStory.ContinueMaximally()); +//etc +``` +**Caveat** : Use with caution, if you have floating point numbers _in your text_, they may be altered. + ## Differences with the C# API There are a few very minor API differences between ink C# and inkjs: diff --git a/package.json b/package.json index 35d8f975..ffc5d69d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inkjs", - "version": "2.3.1", + "version": "2.3.2", "description": "A javascript port of inkle's ink scripting language (http://www.inklestudios.com/ink/)", "type": "commonjs", "main": "dist/ink-full.js", diff --git a/src/engine/JsonSerialisation.ts b/src/engine/JsonSerialisation.ts index d6a8aa3b..14eb462a 100644 --- a/src/engine/JsonSerialisation.ts +++ b/src/engine/JsonSerialisation.ts @@ -315,6 +315,12 @@ export class JsonSerialisation { if (typeof token === "string") { let str = token.toString(); + //Explicit float value of the form "123.00f" + const floatRepresentation = /^([0-9]+.[0-9]+f)$/.exec(str); + if (floatRepresentation) { + return new FloatValue(parseFloat(floatRepresentation[0])); + } + // String value let firstChar = str[0]; if (firstChar == "^") return new StringValue(str.substring(1)); diff --git a/src/engine/SimpleJson.ts b/src/engine/SimpleJson.ts index bc206f13..53e489fa 100644 --- a/src/engine/SimpleJson.ts +++ b/src/engine/SimpleJson.ts @@ -1,17 +1,34 @@ export class SimpleJson { - public static TextToDictionary(text: string) { - return new SimpleJson.Reader(text).ToDictionary(); + public static TextToDictionary( + text: string, + aggressiveFloatParsing: boolean = false + ) { + return new SimpleJson.Reader(text, aggressiveFloatParsing).ToDictionary(); } - public static TextToArray(text: string) { - return new SimpleJson.Reader(text).ToArray(); + public static TextToArray( + text: string, + aggressiveFloatParsing: boolean = false + ) { + return new SimpleJson.Reader(text, aggressiveFloatParsing).ToArray(); } } export namespace SimpleJson { export class Reader { - constructor(text: string) { - this._rootObject = JSON.parse(text); + constructor(text: string, aggressiveFloatParsing: boolean = false) { + if (aggressiveFloatParsing) { + // When the aggressiveFloatParsing argument is true, we aggressively replace + // all floats of the form "123.0" to "123.0f" so that they are recognized + // as such and converted to FloatValue later + const jsonWithExplicitFloat = text.replace( + /(,\s*)([0-9]+\.[0]+)([,]*)/g, + '$1"$2f"$3' + ); + this._rootObject = JSON.parse(jsonWithExplicitFloat); + } else { + this._rootObject = JSON.parse(text); + } } public ToDictionary() { diff --git a/src/engine/Story.ts b/src/engine/Story.ts index 19a83cf4..d2aec516 100644 --- a/src/engine/Story.ts +++ b/src/engine/Story.ts @@ -49,6 +49,10 @@ if (!Number.isInteger) { }; } +interface JSONOptions { + aggressiveFloatParsing: boolean; +} + export class Story extends InkObject { public static inkVersionCurrent = 21; @@ -145,6 +149,7 @@ export class Story extends InkObject { } constructor(contentContainer: Container, lists: ListDefinition[] | null); + constructor(jsonString: string, options: JSONOptions | undefined); constructor(jsonString: string); constructor(json: Record); constructor() { @@ -168,7 +173,16 @@ export class Story extends InkObject { } else { if (typeof arguments[0] === "string") { let jsonString = arguments[0] as string; - json = SimpleJson.TextToDictionary(jsonString); + let aggressiveFloatParsing = false; + + if (typeof arguments[1] != "undefined") { + let options = arguments[1] as JSONOptions | null; + ({ aggressiveFloatParsing } = options || { + aggressiveFloatParsing: false, + }); + } + + json = SimpleJson.TextToDictionary(jsonString, aggressiveFloatParsing); } else { json = arguments[0] as Record; } diff --git a/src/tests/specs/common.ts b/src/tests/specs/common.ts index 26407a27..eb80ca0c 100644 --- a/src/tests/specs/common.ts +++ b/src/tests/specs/common.ts @@ -122,11 +122,14 @@ export class TestContext { export function fromJsonTestContext( name: string, category: string, - testingErrors: boolean = false + testingErrors: boolean = false, + aggressiveFloatParsing: boolean = false ) { let context = new TestContext(testingErrors); let jsonContent = loadJSONFile(name, category); - context.story = new Story(jsonContent); + context.story = new Story(jsonContent, { + aggressiveFloatParsing: aggressiveFloatParsing, + }); context.bytecode = context.story.ToJson(); return context; diff --git a/src/tests/specs/ink/Evaluation.spec.ts b/src/tests/specs/ink/Evaluation.spec.ts index 7030b4d1..95cf4d19 100644 --- a/src/tests/specs/ink/Evaluation.spec.ts +++ b/src/tests/specs/ink/Evaluation.spec.ts @@ -21,13 +21,35 @@ describe("Evaluation", () => { }); // TestArithmetic - it("tests arithmetic", () => { + it("tests arithmetic with compilation", () => { compileStory("arithmetic"); expect(context.story.ContinueMaximally()).toBe( "36\n2\n3\n2\n2.3333333333333335\n8\n8\n" ); }); + it("tests arithmetic with default loadStory", () => { + context = testsUtils.fromJsonTestContext( + "arithmetic", + "evaluation", + false, + false + ); + expect(context.story.ContinueMaximally()).toBe("36\n2\n3\n2\n2\n8\n8\n"); + }); + + it("tests arithmetic with aggressive parse float", () => { + context = testsUtils.fromJsonTestContext( + "arithmetic", + "evaluation", + false, + true + ); + expect(context.story.ContinueMaximally()).toBe( + "36\n2\n3\n2\n2.3333333333333335\n8\n8\n" + ); + }); + // TestBasicStringLiterals it("tests basic string literal", () => { compileStory("basic_string_literals"); diff --git a/src/tests/specs/inkjs/utils/SimpleJson.spec.ts b/src/tests/specs/inkjs/utils/SimpleJson.spec.ts index cb1e5417..eba6a8a5 100644 --- a/src/tests/specs/inkjs/utils/SimpleJson.spec.ts +++ b/src/tests/specs/inkjs/utils/SimpleJson.spec.ts @@ -218,4 +218,17 @@ describe("SimpleJson.Reader", () => { SimpleJson.TextToDictionary(jsonString); }).toThrow(); }); + + it("parses a JSON object string with agressive float parsing", () => { + let jsonString = '{"key":"value", "array": [1, 2, null, 3.0, false]}'; + let object = { + array: [1, 2, null, "3.0f", false], + key: "value", + }; + + let reader = new SimpleJson.Reader(jsonString, true); + + expect(reader.ToDictionary()).toEqual(object); + expect(SimpleJson.TextToDictionary(jsonString, true)).toEqual(object); + }); });