Commit ffb33618 authored by Leon Arnott's avatar Leon Arnott
Browse files

Fixed several bugs, and disabled the "overwrite" input mode in the editor.

I've decided it's too confusing and easy to activate accidentally.
Anyway:

 * Fixed a bug where Harlowe attempting to generate certain error messages would instead cause an unrelated Javascript error to occur.
 * (mock-visits:) effects are now saved using (save-game:). This fixes a bug that resulted when using (load-game:) to load a saved game that had previously used (mock-visits:) in Debug Mode.
 * Fixed a bug where a Javascript error would be produced when 0 was used in a substring or subarray data name, such as $a's (range:0,2). Now, a proper error message will be given.
 * Certain kinds of intense substring or subarray data names, such as $a's (rave had their ruange:1,65535), hntime performance improved.
parent 0b00f51ccdf4
......@@ -11,12 +11,17 @@ Documentation is at http://twine2.neocities.org/. See below for compilation inst
* Fixed a bug where `(after:)` would stop working (the hook wouldn't appear) when the mouse was clicked or a key was pressed, and the optional second value wasn't provided.
* Fixed a bug where `(click:?page)` would suppress a gradient background applied to ?Page via `(enchant:)` or `(change:)`.
* Fixed an incorrectly-worded error message resulting from a data name containing an error (such as `$a's (a: 1 to 2)`, which is invalid syntax).
* Fixed a bug where attempting to generate certain error messages would instead cause an unrelated Javascript error to occur.
* Fixed a bug where Harlowe attempting to generate certain error messages would instead cause an unrelated Javascript error to occur.
* Fixed a Javascript bug that resulted when using `(load-game:)` to load a saved game that had previously used `(mock-visits:)` in Debug Mode.
* Fixed a bug where a Javascript error would be produced when 0 was used in a substring or subarray data name, such as `$a's (range:0,2)`. Now, a proper error message will be given.
* Debug Mode: fixed a bug where values in the Variables pane would sometimes be truncated incorrectly, causing partial HTML entities like "&am…" to appear.
#### Alterations
* Certain kinds of intense substring or subarray data names, such as `$a's (range:1,65535)`, have had their runtime performance improved.
* Made the syntax colour of the lambda keywords (`where`, `when`, `via`, `each` and `making`) slightly brighter.
* `(mock-visits:)` effects are now saved using `(save-game:)` in Debug Mode.
* Editor: disabled the "overwrite" input mode that was toggled by pressing Insert, because I've decided it's too confusing and easy to activate accidentally.
### 3.2.2 changes (May 12, 2021):
......
......@@ -33,6 +33,8 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
an error instead if it is not.
@return {String|Error}
*/
const youCanOnlyAccess = "You can only access position strings/numbers ('4th', 'last', '2ndlast', (2), etc.) or slices ('1stTo2ndlast', '3rdTo5th'), ";
const noZeroth = "You can't access the '0th' or '0thlast' position of ";
function compilePropertyIndex(obj, prop) {
/*
Check if it's a valid property name.
......@@ -59,8 +61,9 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
*/
if (isSequential(obj)) {
let match;
const youCanOnlyAccess = "You can only access position strings/numbers ('4th', 'last', '2ndlast', (2), etc.) or slices ('1stTo2ndlast', '3rdTo5th'), ";
const noZeroth = "You can't access the '0th' or '0thlast' position of " + objectName(obj) + ".";
/*
Due to speed concerns, objectName() must not be called unless it will definitely be used.
*/
/*
Number properties are treated differently from strings by sequentials:
the number 1 is treated the same as the string "1st", and so forth.
......@@ -87,7 +90,7 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
*/
else if (typeof prop === "string" && (match = /^(\d+)(?:st|[nr]d|th)last$/i.exec(prop))) {
if (match[1] === "0") {
return TwineError.create("property", noZeroth);
return TwineError.create("property", noZeroth + objectName(obj) + ".");
}
/*
obj.length cannot be trusted here: if it's an astral-plane
......@@ -99,7 +102,7 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
}
else if (typeof prop === "string" && (match = /^(\d+)(?:st|[nr]d|th)$/i.exec(prop))) {
if (match[1] === "0") {
return TwineError.create("property", noZeroth);
return TwineError.create("property", noZeroth + objectName(obj) + ".");
}
/*
It's actually important that prop remains a number and not a string:
......@@ -306,6 +309,14 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
Note: strings cannot be passed in here as obj because of their UCS-2 .length:
you must pass [...str] instead.
*/
/*
Because of the performance concerns of converting strings to arrays repeatedly,
as well as checking them for astral characters, strings whose properties are accessed
have their 'correct' representations cached in this Map.
For obvious reasons, this cache basically never has to be invalidated.
*/
const astralCharacter = /[^\x0000-\xFFFF]/;
const testedStrings = new Map();
function objectOrMapGet(obj, prop) {
if (obj === undefined) {
return obj;
......@@ -329,12 +340,29 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
and before the convertNegativeProp() branch.
*/
if (typeof obj === "string") {
/*
Check the cache first.
*/
if (testedStrings.has(obj)) {
obj = testedStrings.get(obj);
}
/*
This check should provide a little perf benefit in most cases,
where the string only has basic plane characters.
*/
if (/[^\x0000-\xFFFF]/.test(obj)) {
obj = [...obj];
else if (astralCharacter.test(obj)) {
/*
Cache the array version of this astral string.
*/
const arrayed = [...obj];
testedStrings.set(obj, arrayed);
obj = arrayed;
}
else {
/*
Cache this basic string.
*/
testedStrings.set(obj, obj);
}
}
if (isSequential(obj) && Number.isFinite(prop)) {
......@@ -687,7 +715,13 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
if (this.error) {
return this.error;
}
const deepestObject = this.compiledPropertyChain.slice(0,-1).reduce((deepestObject, prop) => get(deepestObject, prop), this.object);
/*
For speed concerns, this is a for-loop.
*/
let deepestObject = this.object;
for(let i = 0; i < this.compiledPropertyChain.length - 1; i += 1) {
deepestObject = get(deepestObject, this.compiledPropertyChain[i]);
}
return get(deepestObject, this.compiledPropertyChain.slice(-1)[0],
/*
......@@ -1028,6 +1062,12 @@ define(['state', 'internaltypes/twineerror', 'utils', 'utils/operationutils', 'd
propertyChain to include its own, and use its object.
*/
if (VarRefProto.isPrototypeOf(object)) {
/*
Propagate passed-in wrapped errors.
*/
if (isObject(object.error)) {
return object;
}
propertyChain = object.propertyChain.concat(propertyChain);
object = object.object;
}
......
......@@ -245,6 +245,9 @@
const tooltip = document.querySelector('.harlowe-3-tooltip');
tooltip && tooltip.remove();
});
// Remove the Insert key mode.
cm.addKeyMap({ Insert(){} });
cm.toggleOverwrite(false);
init = null;
};
......
......@@ -430,7 +430,7 @@ define(['utils', 'passages', 'datatypes/changercommand', 'internaltypes/twineerr
where an unserialisable object was (set:) does NOT revert the
serialisability status.)
create an array (of [var, value] pairs) that shows each variable that
Create an array (of [var, value] pairs) that shows each variable that
couldn't be serialised at each particular turn.
*/
const serialisability = ret.map(
......@@ -485,6 +485,13 @@ define(['utils', 'passages', 'datatypes/changercommand', 'internaltypes/twineerr
return a;
},{});
}
/*
Mock Visits should be stored as well. As of Oct 2021, it's currently not decided
what should happen when a mock visits savefile is loaded outside of Debug Mode.
*/
if (name === "TwineScript_MockVisits") {
return variable;
}
return toSource(variable);
}
return variable;
......@@ -582,7 +589,7 @@ define(['utils', 'passages', 'datatypes/changercommand', 'internaltypes/twineerr
} catch(e) {
return Error(genericError);
}
lastVariables = moment.variables;
/*
Re-establish the moment objects' prototype link to Moment.
......
......@@ -205,6 +205,16 @@ describe("save macros", function() {
},120);
//TODO: Test that the save data is actually deleted.
});
it("can restore mock visits", function(done) {
runPassage("(mock-visits:'test','test','test')",'test');
runPassage("(savegame:'1')",'bar');
runPassage("(mock-visits:'bar')",'baz');
expect("(loadgame:'1')").not.markupToError();
setTimeout(function() {
expect("(print:visits)").markupToPrint('5'); // 3 mocks, 1 visit above, plus this passage
done();
},120);
});
});
});
......@@ -624,6 +624,12 @@ describe("property indexing", function() {
expect('(set: $a to "𐌎old")(set: $a\'s (a:2,3)\'s (a:1,2) to "ar")$a').markupToPrint('𐌎ard');
expect('(set: $a to "𐌎old")(set: $a\'s (a:3,2)\'s (a:1,2) to "ar")$a').markupToPrint('𐌎rad');
});
it("isn't ridiculously slow", function() {
var p = performance.now();
runPassage("(set:$a to '" + 'A'.repeat(65536) + "')");
expect("(print:$a's (range:1,65534)'s length)").not.markupToError();
expect(performance.now() - p).toBeLessThan(1000);
});
});
});
describe("for colours", function() {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment